From 0bbb1bad5af7a289d78700d48af482d9339dce6f Mon Sep 17 00:00:00 2001 From: Stilic <63605602+Stilic@users.noreply.github.com> Date: Wed, 8 Sep 2021 13:34:37 +0200 Subject: Fix upload size --- api/src/Server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/Server.ts b/api/src/Server.ts index 0f444f86..b9ca3fba 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -37,7 +37,7 @@ export class FosscordServer extends Server { await initEvent(); this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: 1024 * 1024 * 10 })); // 10MB + this.app.use(BodyParser({ inflate: true, limit: "10mb" })); const app = this.app; const api = Router(); // @ts-ignore -- cgit 1.5.1 From 3cd404eb422fb2bac358264227839f15ba241f45 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 9 Sep 2021 18:32:36 +0200 Subject: Fix relationships get query --- api/src/routes/users/@me/relationships.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 8d6d8c9e..995b0244 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -19,7 +19,7 @@ const router = Router(); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["relationships"] }); + const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] }); return res.json(user.relationships); }); -- cgit 1.5.1 From c073adf3ba3a46651a43cf995383071b2496c918 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Sun, 12 Sep 2021 19:27:29 +0200 Subject: Add dummy missing routes --- api/src/routes/applications/detectable.ts | 10 ++++++++++ api/src/routes/outbound-promotions.ts | 10 ++++++++++ api/src/routes/users/@me/affinities/user.ts | 10 ---------- api/src/routes/users/@me/affinities/users.ts | 10 ++++++++++ api/src/routes/users/@me/applications/#app_id/entitlements.ts | 10 ++++++++++ api/src/routes/users/@me/billing/country-code.ts | 10 ++++++++++ api/src/routes/users/@me/billing/subscriptions.ts | 10 ++++++++++ 7 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 api/src/routes/applications/detectable.ts create mode 100644 api/src/routes/outbound-promotions.ts delete mode 100644 api/src/routes/users/@me/affinities/user.ts create mode 100644 api/src/routes/users/@me/affinities/users.ts create mode 100644 api/src/routes/users/@me/applications/#app_id/entitlements.ts create mode 100644 api/src/routes/users/@me/billing/country-code.ts create mode 100644 api/src/routes/users/@me/billing/subscriptions.ts (limited to 'api/src') diff --git a/api/src/routes/applications/detectable.ts b/api/src/routes/applications/detectable.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/applications/detectable.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/outbound-promotions.ts b/api/src/routes/outbound-promotions.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/outbound-promotions.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/affinities/user.ts b/api/src/routes/users/@me/affinities/user.ts deleted file mode 100644 index 0790a8a4..00000000 --- a/api/src/routes/users/@me/affinities/user.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Router, Response, Request } from "express"; - -const router = Router(); - -router.get("/", (req: Request, res: Response) => { - // TODO: - res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); -}); - -export default router; diff --git a/api/src/routes/users/@me/affinities/users.ts b/api/src/routes/users/@me/affinities/users.ts new file mode 100644 index 00000000..db67ab71 --- /dev/null +++ b/api/src/routes/users/@me/affinities/users.ts @@ -0,0 +1,10 @@ +import { Router, Response, Request } from "express"; + +const router = Router(); + +router.get("/", (req: Request, res: Response) => { + //TODO + res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); +}); + +export default router; diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/api/src/routes/users/@me/applications/#app_id/entitlements.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/users/@me/applications/#app_id/entitlements.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/billing/country-code.ts b/api/src/routes/users/@me/billing/country-code.ts new file mode 100644 index 00000000..ac3653a2 --- /dev/null +++ b/api/src/routes/users/@me/billing/country-code.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json({ "country_code": "US" }).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/api/src/routes/users/@me/billing/subscriptions.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/users/@me/billing/subscriptions.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file -- cgit 1.5.1 From 60535f5159a8674874c0a2e319d46400184bd22e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 21:09:29 +0200 Subject: :art: remove long relatives paths -> short module paths --- api/package-lock.json | 76 +- api/package.json | 3 +- api/scripts/tsconfig-paths-bootstrap.js | 10 + api/src/index.ts | 11 +- api/src/middlewares/ErrorHandler.ts | 2 +- api/src/middlewares/RateLimit.ts | 2 +- api/src/routes/auth/login.ts | 2 +- api/src/routes/auth/register.ts | 4 +- api/src/routes/channels/#channel_id/index.ts | 2 +- api/src/routes/channels/#channel_id/invites.ts | 4 +- .../#channel_id/messages/#message_id/ack.ts | 2 +- .../#channel_id/messages/#message_id/index.ts | 4 +- .../channels/#channel_id/messages/bulk-delete.ts | 2 +- .../routes/channels/#channel_id/messages/index.ts | 6 +- api/src/routes/channels/#channel_id/permissions.ts | 2 +- api/src/routes/channels/#channel_id/webhooks.ts | 2 +- api/src/routes/guilds/#guild_id/bans.ts | 5 +- api/src/routes/guilds/#guild_id/channels.ts | 2 +- api/src/routes/guilds/#guild_id/index.ts | 4 +- .../guilds/#guild_id/members/#member_id/index.ts | 4 +- api/src/routes/guilds/#guild_id/members/index.ts | 2 +- api/src/routes/guilds/#guild_id/regions.ts | 6 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- api/src/routes/guilds/#guild_id/templates.ts | 4 +- api/src/routes/guilds/#guild_id/vanity-url.ts | 2 +- .../#guild_id/voice-states/#user_id/index.ts | 8 +- .../guilds/#guild_id/voice-states/@me/index.ts | 8 +- api/src/routes/guilds/#guild_id/welcome_screen.ts | 2 +- api/src/routes/guilds/#guild_id/widget.json.ts | 2 +- api/src/routes/guilds/#guild_id/widget.ts | 2 +- api/src/routes/guilds/index.ts | 2 +- api/src/routes/guilds/templates/index.ts | 2 +- api/src/routes/users/#id/index.ts | 2 +- api/src/routes/users/#id/profile.ts | 2 +- api/src/routes/users/@me/channels.ts | 6 +- api/src/routes/users/@me/index.ts | 4 +- api/src/routes/users/@me/relationships.ts | 2 +- api/src/routes/users/@me/settings.ts | 2 +- api/src/routes/voice/regions.ts | 6 +- api/src/test/password_test.ts | 12 +- api/src/util/index.ts | 11 + api/src/util/passwordStrength.ts | 2 +- api/tests/automatic.test.js | 0 api/tsconfig.json | 7 +- bundle/package-lock.json | 83 +- bundle/package.json | 5 +- bundle/tsconfig-paths-bootstrap.js | 14 + cdn/package.json | 2 +- cdn/scripts/tsconfig-paths-bootstrap.js | 10 + cdn/src/routes/attachments.ts | 120 ++- cdn/src/routes/avatars.ts | 70 +- cdn/src/routes/external.ts | 5 +- cdn/tsconfig.json | 7 +- gateway/package-lock.json | 1056 +++++++++++++++++++- gateway/package.json | 3 +- gateway/scripts/tsconfig-paths-bootstrap.js | 10 + gateway/src/events/Close.ts | 2 +- gateway/src/events/Connection.ts | 23 +- gateway/src/events/Message.ts | 13 +- gateway/src/index.ts | 3 + gateway/src/listener/listener.ts | 10 +- gateway/src/opcodes/Heartbeat.ts | 8 +- gateway/src/opcodes/Identify.ts | 10 +- gateway/src/opcodes/LazyRequest.ts | 6 +- gateway/src/opcodes/PresenceUpdate.ts | 4 +- gateway/src/opcodes/RequestGuildMembers.ts | 4 +- gateway/src/opcodes/Resume.ts | 6 +- gateway/src/opcodes/VoiceStateUpdate.ts | 46 +- gateway/src/opcodes/index.ts | 4 +- gateway/src/opcodes/instanceOf.ts | 4 +- gateway/src/util/Send.ts | 2 +- gateway/src/util/WebSocket.ts | 4 +- gateway/src/util/index.ts | 5 + gateway/tsconfig.json | 7 +- util/package-lock.json | 67 ++ util/package.json | 1 + util/src/interfaces/Event.ts | 45 + util/src/util/Constants.ts | 2 +- util/src/util/Database.ts | 2 +- 79 files changed, 1641 insertions(+), 269 deletions(-) create mode 100644 api/scripts/tsconfig-paths-bootstrap.js create mode 100644 api/src/util/index.ts create mode 100644 api/tests/automatic.test.js create mode 100644 bundle/tsconfig-paths-bootstrap.js create mode 100644 cdn/scripts/tsconfig-paths-bootstrap.js create mode 100644 gateway/scripts/tsconfig-paths-bootstrap.js create mode 100644 gateway/src/util/index.ts (limited to 'api/src') diff --git a/api/package-lock.json b/api/package-lock.json index 7b1e000b..8b2c93f8 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -38,6 +38,7 @@ "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "supertest": "^6.1.6", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37" }, "devDependencies": { @@ -79,12 +80,13 @@ "env-paths": "^2.2.1", "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.10", - "missing-native-js-functions": "^1.2.14", + "missing-native-js-functions": "^1.2.15", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -1396,6 +1398,11 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "node_modules/@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -11344,6 +11351,36 @@ "strip-json-comments": "^2.0.0" } }, + "node_modules/tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "engines": { + "node": ">=4" + } + }, "node_modules/tsconfig/node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -12977,12 +13014,13 @@ "jest": "^27.0.6", "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.10", - "missing-native-js-functions": "^1.2.14", + "missing-native-js-functions": "^1.2.15", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -13574,6 +13612,11 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -18703,8 +18746,7 @@ "dev": true }, "mpath": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", + "version": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" }, "mquery": { @@ -21726,6 +21768,32 @@ } } }, + "tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", diff --git a/api/package.json b/api/package.json index a501fb15..b762b944 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "npm run build && jest --coverage --verbose --forceExit ./tests", "test:watch": "jest --watch", - "start": "npm run build && node dist/start", + "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start", "build": "npx tsc -b .", "build-docker": "tsc -p tsconfig-docker.json", "dev": "tsnd --respawn src/start.ts", @@ -86,6 +86,7 @@ "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "supertest": "^6.1.6", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37" }, "jest": { diff --git a/api/scripts/tsconfig-paths-bootstrap.js b/api/scripts/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..d6ad3c57 --- /dev/null +++ b/api/scripts/tsconfig-paths-bootstrap.js @@ -0,0 +1,10 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, ".."), + paths: { + "@fosscord/api": ["dist/index.js"], + "@fosscord/api/*": ["dist/*"] + } +}); diff --git a/api/src/index.ts b/api/src/index.ts index fe59310f..adc7649c 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,12 +1,3 @@ export * from "./Server"; export * from "./middlewares/"; -export * from "./schema/Ban"; -export * from "./schema/Channel"; -export * from "./schema/Guild"; -export * from "./schema/Invite"; -export * from "./schema/Message"; -export * from "./util/instanceOf"; -export * from "./util/instanceOf"; -export * from "./util/RandomInviteID"; -export * from "./util/String"; -export { check as checkPassword } from "./util/passwordStrength"; +export * from "./util/"; diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index be2586cf..d288f3fb 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { EntityNotFoundError } from "typeorm"; -import { FieldError } from "../util/instanceOf"; +import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index dffbc0d9..d1fd072f 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,6 +1,6 @@ import { Config, listenEvent } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { getIpAdress } from "../util/ipAddress"; +import { getIpAdress } from "@fosscord/api"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; // Docs: https://discord.com/developers/docs/topics/rate-limits diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index 7fd0f870..2e2f763d 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { check, FieldErrors, Length } from "../../util/instanceOf"; +import { check, FieldErrors, Length } from "@fosscord/api"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Config, User } from "@fosscord/util"; diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index a9518e91..d3c85778 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,10 +1,10 @@ import { Request, Response, Router } from "express"; import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; import bcrypt from "bcrypt"; -import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; +import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "@fosscord/api"; import "missing-native-js-functions"; import { generateToken } from "./login"; -import { getIpAdress, IPAnalysis, isProxy } from "../../util/ipAddress"; +import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import { HTTPError } from "lambert-server"; import { In } from "typeorm"; diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 4aa5a5b9..46554d70 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -2,7 +2,7 @@ import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermissi import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel // TODO: Get channel diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index fe22d3bc..c6909fd0 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; -import { random } from "../../../util/RandomInviteID"; +import { check } from "@fosscord/api"; +import { random } from "@fosscord/api"; import { InviteCreateSchema } from "../../../schema/Invite"; import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; import { isTextChannel } from "./messages"; diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index 0fd5f2be..aab51484 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -1,7 +1,7 @@ import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { check } from "../../../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 7a00de43..e9588bd3 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,8 +1,8 @@ import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { MessageCreateSchema } from "../../../../../schema/Message"; -import { check } from "../../../../../util/instanceOf"; -import { handleMessage, postHandleMessage } from "../../../../../util/Message"; +import { check } from "@fosscord/api"; +import { handleMessage, postHandleMessage } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 5c486676..5d7566e1 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -2,7 +2,7 @@ import { Router, Response, Request } from "express"; import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index ad590d05..591ebbbe 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -2,11 +2,11 @@ import { Router, Response, Request } from "express"; import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../schema/Message"; -import { check, instanceOf, Length } from "../../../../util/instanceOf"; +import { check, instanceOf, Length } from "@fosscord/api"; import multer from "multer"; import { Query } from "mongoose"; -import { sendMessage } from "../../../../util/Message"; -import { uploadFile } from "../../../../util/cdn"; +import { sendMessage } from "@fosscord/api"; +import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 9c49542b..0465ca31 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -2,7 +2,7 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index e4125879..821a62db 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { check, Length } from "../../../util/instanceOf"; +import { check, Length } from "@fosscord/api"; import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 31aa2385..86bff6b4 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,9 +1,8 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { getIpAdress } from "../../../util/ipAddress"; -import { BanCreateSchema } from "../../../schema/Ban"; -import { check } from "../../../util/instanceOf"; +import { getIpAdress, check } from "@fosscord/api"; +import { BanCreateSchema } from "@fosscord/api/schema/Ban"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index 5aa1d33d..faeecb76 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -3,7 +3,7 @@ import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router = Router(); router.get("/", async (req: Request, res: Response) => { diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 9d302a48..244900ec 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -3,8 +3,8 @@ import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@foss import { HTTPError } from "lambert-server"; import { GuildUpdateSchema } from "../../../schema/Guild"; -import { check } from "../../../util/instanceOf"; -import { handleFile } from "../../../util/cdn"; +import { check } from "@fosscord/api"; +import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 0d62e555..8b04a508 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -11,8 +11,8 @@ import { emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../../../util/instanceOf"; -import { MemberChangeSchema } from "../../../../../schema/Member"; +import { check } from "@fosscord/api"; +import { MemberChangeSchema } from "@fosscord/api/schema/Member"; import { In } from "typeorm"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 0bfd71cb..198d6946 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,6 +1,6 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length } from "../../../../util/instanceOf"; +import { instanceOf, Length } from "@fosscord/api"; import { MoreThan } from "typeorm"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts index 212c9bcd..86208b79 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/api/src/routes/guilds/#guild_id/regions.ts @@ -1,7 +1,7 @@ -import {Config, Guild, Member} from "@fosscord/util"; +import { Config, Guild, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import {getVoiceRegions} from "../../../util/Voice"; -import {getIpAdress} from "../../../util/ipAddress"; +import { getVoiceRegions } from "@fosscord/api"; +import { getIpAdress } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 6a318688..76dd47c5 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -12,7 +12,7 @@ import { } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { RoleModifySchema, RolePositionUpdateSchema } from "../../../schema/Roles"; import { DiscordApiErrors } from "@fosscord/util"; import { In } from "typeorm"; diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index a7613abf..e9304e11 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -2,8 +2,8 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; -import { check } from "../../../util/instanceOf"; -import { generateCode } from "../../../util/String"; +import { check } from "@fosscord/api"; +import { generateCode } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 58940b42..f1887cc0 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,7 +1,7 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { check, Length } from "../../../util/instanceOf"; +import { check, Length } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 02951f81..447e15c1 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,15 +1,15 @@ -import { check } from "../../../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { VoiceStateUpdateSchema } from "../../../../../schema"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "../../../../../util/VoiceState"; +import { updateVoiceState } from "@fosscord/api"; const router = Router(); router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; const { guild_id, user_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id, user_id) + await updateVoiceState(body, guild_id, req.user_id, user_id); return res.sendStatus(204); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts index 42ba543e..b637ff66 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts @@ -1,15 +1,15 @@ -import { check } from "../../../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { VoiceStateUpdateSchema } from "../../../../../schema"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "../../../../../util/VoiceState"; +import { updateVoiceState } from "@fosscord/api"; const router = Router(); router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; const { guild_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id) + await updateVoiceState(body, guild_id, req.user_id); return res.sendStatus(204); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index defbcd40..7ca49b4e 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -2,7 +2,7 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index 193ed095..f871fac8 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { random } from "../../../util/RandomInviteID"; +import { random } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index fcf71402..d9ce817e 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { getPermission, Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { WidgetModifySchema } from "../../../schema/Widget"; const router: Router = Router(); diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index e5830647..a1b199e7 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "./../../util/instanceOf"; +import { check } from "@fosscord/api"; import { GuildCreateSchema } from "../../schema/Guild"; import { DiscordApiErrors } from "@fosscord/util"; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index 58201f65..1d0f2716 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -3,7 +3,7 @@ const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; router.get("/:code", async (req: Request, res: Response) => { diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts index 3841756b..07956360 100644 --- a/api/src/routes/users/#id/index.ts +++ b/api/src/routes/users/#id/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { User } from "../../../../../util/dist"; +import { User } from "@fosscord/util"; const router: Router = Router(); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 8be03b47..0f43a82f 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { PublicConnectedAccount, PublicUser, User, UserPublic } from "../../../../../util/dist"; +import { PublicConnectedAccount, PublicUser, User, UserPublic } from "@fosscord/util"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 6fd396b8..77fc8296 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,11 +1,9 @@ import { Router, Request, Response } from "express"; -import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent } from "@fosscord/util"; +import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - import { DmChannelCreateSchema } from "../../../schema/Channel"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { In } from "typeorm"; -import { Recipient } from "../../../../../util/dist/entities/Recipient"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 68649215..451a657c 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,8 +1,8 @@ import { Router, Request, Response } from "express"; import { User, PrivateUserProjection } from "@fosscord/util"; import { UserModifySchema } from "../../../schema/User"; -import { check } from "../../../util/instanceOf"; -import { handleFile } from "../../../util/cdn"; +import { check } from "@fosscord/api"; +import { handleFile } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 995b0244..67ca2f35 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -12,7 +12,7 @@ import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { DiscordApiErrors } from "@fosscord/util"; -import { check, Length } from "../../../util/instanceOf"; +import { check, Length } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index 90ee6372..e7db85ed 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -1,6 +1,6 @@ import { Router, Response, Request } from "express"; import { User, UserSettings } from "@fosscord/util"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { UserSettingsSchema } from "../../../schema/User"; const router = Router(); diff --git a/api/src/routes/voice/regions.ts b/api/src/routes/voice/regions.ts index 812aa8f6..da1aaade 100644 --- a/api/src/routes/voice/regions.ts +++ b/api/src/routes/voice/regions.ts @@ -1,11 +1,11 @@ import { Router, Request, Response } from "express"; -import {getIpAdress} from "../../util/ipAddress"; -import {getVoiceRegions} from "../../util/Voice"; +import { getIpAdress } from "@fosscord/api"; +import { getVoiceRegions } from "@fosscord/api"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - res.json(await getVoiceRegions(getIpAdress(req), true))//vip true? + res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true? }); export default router; diff --git a/api/src/test/password_test.ts b/api/src/test/password_test.ts index 59d36621..983b18ae 100644 --- a/api/src/test/password_test.ts +++ b/api/src/test/password_test.ts @@ -1,12 +1,12 @@ -import { check } from "./../util/passwordStrength"; +import { checkPassword } from "@fosscord/api"; -console.log(check("123456789012345")); +console.log(checkPassword("123456789012345")); // -> 0.25 -console.log(check("ABCDEFGHIJKLMOPQ")); +console.log(checkPassword("ABCDEFGHIJKLMOPQ")); // -> 0.25 -console.log(check("ABC123___...123")); +console.log(checkPassword("ABC123___...123")); // -> -console.log(check("")); +console.log(checkPassword("")); // -> -// console.log(check("")); +// console.log(checkPassword("")); // // -> diff --git a/api/src/util/index.ts b/api/src/util/index.ts new file mode 100644 index 00000000..43481289 --- /dev/null +++ b/api/src/util/index.ts @@ -0,0 +1,11 @@ +export * from "./Base64"; +export * from "./cdn"; +export * from "./instanceOf"; +export * from "./ipAddress"; +export * from "./Message"; +export * from "./passwordStrength"; +export * from "./RandomInviteID"; +export * from "./route"; +export * from "./String"; +export * from "./Voice"; +export * from "./VoiceState"; diff --git a/api/src/util/passwordStrength.ts b/api/src/util/passwordStrength.ts index dfffa2c0..047df008 100644 --- a/api/src/util/passwordStrength.ts +++ b/api/src/util/passwordStrength.ts @@ -16,7 +16,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored * * Returns: 0 > pw > 1 */ -export function check(password: string): number { +export function checkPassword(password: string): number { const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; var strength = 0; diff --git a/api/tests/automatic.test.js b/api/tests/automatic.test.js new file mode 100644 index 00000000..e69de29b diff --git a/api/tsconfig.json b/api/tsconfig.json index 6bf2e6b7..21a8eb96 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -63,6 +63,11 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "baseUrl": ".", + "paths": { + "@fosscord/api": ["src/index.ts"], + "@fosscord/api/*": ["src/*"] + } } } diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 944570ed..0092e0d5 100644 --- a/bundle/package-lock.json +++ b/bundle/package-lock.json @@ -17,7 +17,8 @@ "async-exit-hook": "^2.0.1", "express": "^4.17.1", "missing-native-js-functions": "^1.2.15", - "node-os-utils": "^1.3.5" + "node-os-utils": "^1.3.5", + "tsconfig-paths": "^3.11.0" }, "devDependencies": { "@types/amqplib": "^0.8.1", @@ -73,6 +74,7 @@ "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "supertest": "^6.1.6", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37" }, "devDependencies": { @@ -158,6 +160,7 @@ "missing-native-js-functions": "^1.2.15", "mongoose-autopopulate": "^0.12.3", "node-fetch": "^2.6.1", + "typeorm": "^0.2.37", "uuid": "^8.3.2", "ws": "^7.4.2" }, @@ -194,6 +197,7 @@ "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -326,6 +330,11 @@ "i18next": ">=17.0.11" } }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "node_modules/@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -795,6 +804,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -846,6 +866,11 @@ "node": ">= 0.6" } }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, "node_modules/missing-native-js-functions": { "version": "1.2.15", "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.15.tgz", @@ -1025,6 +1050,14 @@ "node": ">= 0.6" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "engines": { + "node": ">=4" + } + }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -1033,6 +1066,17 @@ "node": ">=0.6" } }, + "node_modules/tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1145,6 +1189,7 @@ "supertest": "^6.1.6", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -1211,6 +1256,7 @@ "mongoose-autopopulate": "^0.12.3", "node-fetch": "^2.6.1", "ts-node-dev": "^1.1.6", + "typeorm": "^0.2.37", "typescript": "^4.2.3", "uuid": "^8.3.2", "ws": "^7.4.2" @@ -1238,6 +1284,7 @@ "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -1334,6 +1381,11 @@ "i18next": ">=17.0.11" } }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -1731,6 +1783,14 @@ "has": "^1.0.3" } }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1764,6 +1824,11 @@ "mime-db": "1.49.0" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, "missing-native-js-functions": { "version": "1.2.15", "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.15.tgz", @@ -1912,11 +1977,27 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/bundle/package.json b/bundle/package.json index a326a131..996ab30b 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -11,7 +11,7 @@ "build:api": "cd ../api/ && npm run build", "build:cdn": "cd ../cdn/ && npm run build", "build:gateway": "cd ../gateway/ && npm run build", - "start": "npm run build && node dist/start.js", + "start": "npm run build && node -r ./tsconfig-paths-bootstrap.js dist/start.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -52,6 +52,7 @@ "async-exit-hook": "^2.0.1", "express": "^4.17.1", "missing-native-js-functions": "^1.2.15", - "node-os-utils": "^1.3.5" + "node-os-utils": "^1.3.5", + "tsconfig-paths": "^3.11.0" } } diff --git a/bundle/tsconfig-paths-bootstrap.js b/bundle/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..efe4b3bc --- /dev/null +++ b/bundle/tsconfig-paths-bootstrap.js @@ -0,0 +1,14 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, "node_modules", "@fosscord"), + paths: { + "@fosscord/api": ["api/dist/index.js"], + "@fosscord/api/*": ["api/dist/*"], + "@fosscord/gateway": ["gateway/dist/index.js"], + "@fosscord/gateway/*": ["gateway/dist/*"], + "@fosscord/cdn": ["cdn/dist/index.js"], + "@fosscord/cdn/*": ["cdn/dist/*"], + }, +}); diff --git a/cdn/package.json b/cdn/package.json index c2bdbd79..7c59381b 100644 --- a/cdn/package.json +++ b/cdn/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "npm run build && jest --coverage ./tests", "build": "npx tsc -b .", - "start": "npm run build && node dist/start.js" + "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js" }, "repository": { "type": "git", diff --git a/cdn/scripts/tsconfig-paths-bootstrap.js b/cdn/scripts/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..50602b02 --- /dev/null +++ b/cdn/scripts/tsconfig-paths-bootstrap.js @@ -0,0 +1,10 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, ".."), + paths: { + "@fosscord/cdn": ["dist/index.js"], + "@fosscord/cdn/*": ["dist/*"], + }, +}); diff --git a/cdn/src/routes/attachments.ts b/cdn/src/routes/attachments.ts index 59845c94..7c55998b 100644 --- a/cdn/src/routes/attachments.ts +++ b/cdn/src/routes/attachments.ts @@ -1,73 +1,87 @@ import { Router, Response, Request } from "express"; import { Config, Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; +import { storage } from "@fosscord/cdn/util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; -import { multer } from "../util/multer"; +import { multer } from "@fosscord/cdn/util/multer"; import imageSize from "image-size"; const router = Router(); -router.post("/:channel_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("file missing"); - - const { buffer, mimetype, size, originalname, fieldname } = req.file; - const { channel_id } = req.params; - const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); - const id = Snowflake.generate(); - const path = `attachments/${channel_id}/${id}/${filename}`; - - const endpoint = Config.get()?.cdn.endpoint || "http://localhost:3003"; - - await storage.set(path, buffer); - var width; - var height; - if (mimetype.includes("image")) { - const dimensions = imageSize(buffer); - if (dimensions) { - width = dimensions.width; - height = dimensions.height; +router.post( + "/:channel_id", + multer.single("file"), + async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("file missing"); + + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { channel_id } = req.params; + const filename = originalname + .replaceAll(" ", "_") + .replace(/[^a-zA-Z0-9._]+/g, ""); + const id = Snowflake.generate(); + const path = `attachments/${channel_id}/${id}/${filename}`; + + const endpoint = Config.get()?.cdn.endpoint || "http://localhost:3003"; + + await storage.set(path, buffer); + var width; + var height; + if (mimetype.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + width = dimensions.width; + height = dimensions.height; + } } - } - - const file = { - id, - content_type: mimetype, - filename: filename, - size, - url: `${endpoint}/${path}`, - width, - height, - }; - return res.json(file); -}); + const file = { + id, + content_type: mimetype, + filename: filename, + size, + url: `${endpoint}/${path}`, + width, + height, + }; + + return res.json(file); + } +); -router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => { - const { channel_id, id, filename } = req.params; +router.get( + "/:channel_id/:id/:filename", + async (req: Request, res: Response) => { + const { channel_id, id, filename } = req.params; - const file = await storage.get(`attachments/${channel_id}/${id}/${filename}`); - if (!file) throw new HTTPError("File not found"); - const type = await FileType.fromBuffer(file); + const file = await storage.get( + `attachments/${channel_id}/${id}/${filename}` + ); + if (!file) throw new HTTPError("File not found"); + const type = await FileType.fromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); -}); + return res.send(file); + } +); -router.delete("/:channel_id/:id/:filename", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); +router.delete( + "/:channel_id/:id/:filename", + async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); - const { channel_id, id, filename } = req.params; - const path = `attachments/${channel_id}/${id}/${filename}`; + const { channel_id, id, filename } = req.params; + const path = `attachments/${channel_id}/${id}/${filename}`; - await storage.delete(path); + await storage.delete(path); - return res.send({ success: true }); -}); + return res.send({ success: true }); + } +); export default router; diff --git a/cdn/src/routes/avatars.ts b/cdn/src/routes/avatars.ts index 03388afc..3d745f90 100644 --- a/cdn/src/routes/avatars.ts +++ b/cdn/src/routes/avatars.ts @@ -1,9 +1,9 @@ import { Router, Response, Request } from "express"; import { Config, Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; +import { storage } from "@fosscord/cdn/util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; -import { multer } from "../util/multer"; +import { multer } from "@fosscord/cdn/util/multer"; import crypto from "crypto"; // TODO: check premium and animated pfp are allowed in the config @@ -12,36 +12,50 @@ import crypto from "crypto"; // TODO: delete old icons const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; -const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; +const STATIC_MIME_TYPES = [ + "image/png", + "image/jpeg", + "image/webp", + "image/svg+xml", + "image/svg", +]; const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); -router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, mimetype, size, originalname, fieldname } = req.file; - const { user_id } = req.params; - - var hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); - - const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); - if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash - - const path = `avatars/${user_id}/${hash}`; - const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; - - await storage.set(path, buffer); - - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, - }); -}); +router.post( + "/:user_id", + multer.single("file"), + async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { user_id } = req.params; + + var hash = crypto + .createHash("md5") + .update(Snowflake.generate()) + .digest("hex"); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) + throw new HTTPError("Invalid file type"); + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + + const path = `avatars/${user_id}/${hash}`; + const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; + + await storage.set(path, buffer); + + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, + }); + } +); router.get("/:user_id/:hash", async (req: Request, res: Response) => { var { user_id, hash } = req.params; diff --git a/cdn/src/routes/external.ts b/cdn/src/routes/external.ts index 10bb0f7d..e7783ec3 100644 --- a/cdn/src/routes/external.ts +++ b/cdn/src/routes/external.ts @@ -2,7 +2,7 @@ import { Router, Response, Request } from "express"; import fetch from "node-fetch"; import { HTTPError } from "lambert-server"; import { Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; +import { storage } from "@fosscord/cdn/util/Storage"; import FileType from "file-type"; import { Config } from "@fosscord/util"; @@ -13,7 +13,8 @@ const DEFAULT_FETCH_OPTIONS: any = { redirect: "follow", follow: 1, headers: { - "user-agent": "Mozilla/5.0 (compatible Fosscordbot/0.1; +https://fosscord.com)", + "user-agent": + "Mozilla/5.0 (compatible Fosscordbot/0.1; +https://fosscord.com)", }, size: 1024 * 1024 * 8, compress: true, diff --git a/cdn/tsconfig.json b/cdn/tsconfig.json index 08e39435..2daac1ad 100644 --- a/cdn/tsconfig.json +++ b/cdn/tsconfig.json @@ -65,6 +65,11 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "baseUrl": ".", + "paths": { + "@fosscord/cdn/": ["src/index.ts"], + "@fosscord/cdn/*": ["src/*"] + } } } diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 340f595d..64c542e4 100644 --- a/gateway/package-lock.json +++ b/gateway/package-lock.json @@ -18,6 +18,7 @@ "missing-native-js-functions": "^1.2.15", "mongoose-autopopulate": "^0.12.3", "node-fetch": "^2.6.1", + "typeorm": "^0.2.37", "uuid": "^8.3.2", "ws": "^7.4.2" }, @@ -48,12 +49,13 @@ "env-paths": "^2.2.1", "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.10", - "missing-native-js-functions": "^1.2.14", + "missing-native-js-functions": "^1.2.15", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -71,6 +73,11 @@ "resolved": "../util", "link": true }, + "node_modules/@sqltools/formatter": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" + }, "node_modules/@types/amqplib": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.8.1.tgz", @@ -183,6 +190,11 @@ "@types/node": "*" } }, + "node_modules/@types/zen-observable": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -226,6 +238,14 @@ "node": ">=10" } }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -240,6 +260,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -253,12 +278,25 @@ "node": ">= 8" } }, + "node_modules/app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -273,8 +311,26 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -378,7 +434,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -405,6 +460,29 @@ "node": ">=0.6.19" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -465,6 +543,53 @@ "fsevents": "~2.3.2" } }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -496,8 +621,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/content-disposition": { "version": "0.5.3", @@ -625,6 +749,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -633,11 +762,27 @@ "node": ">= 0.8" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -704,6 +849,14 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -766,8 +919,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { "version": "2.3.2", @@ -789,11 +941,18 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -833,6 +992,25 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -849,6 +1027,14 @@ "node": ">=10.0.0" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -875,11 +1061,29 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -931,6 +1135,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -957,6 +1169,17 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1129,7 +1352,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1152,7 +1374,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -1302,6 +1523,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -1327,6 +1558,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -1342,7 +1581,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1356,6 +1594,32 @@ "node": ">=4" } }, + "node_modules/parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1368,7 +1632,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1480,12 +1743,25 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "node_modules/regexp-clone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==", "peer": true }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1561,6 +1837,11 @@ "node": ">=6" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1616,6 +1897,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/sift": { "version": "13.5.2", "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", @@ -1670,6 +1963,30 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -1699,6 +2016,25 @@ "node": ">=8" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1800,6 +2136,11 @@ "strip-json-comments": "^2.0.0" } }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1812,6 +2153,121 @@ "node": ">= 0.6" } }, + "node_modules/typeorm": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.37.tgz", + "integrity": "sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==", + "dependencies": { + "@sqltools/formatter": "^1.2.2", + "app-root-path": "^3.0.0", + "buffer": "^6.0.3", + "chalk": "^4.1.0", + "cli-highlight": "^2.1.11", + "debug": "^4.3.1", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "mkdirp": "^1.0.4", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.1.0", + "xml2js": "^0.4.23", + "yargonaut": "^1.1.4", + "yargs": "^17.0.1", + "zen-observable-ts": "^1.0.0" + }, + "bin": { + "typeorm": "cli.js" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@sap/hana-client": "*", + "better-sqlite3": "*", + "hdb-pool": "*", + "ioredis": "*", + "mongodb": "^3.6.0", + "mssql": "*", + "mysql2": "*", + "oracledb": "*", + "pg": "*", + "pg-native": "*", + "pg-query-stream": "*", + "redis": "*", + "sql.js": "*", + "sqlite3": "*", + "typeorm-aurora-data-api-driver": "*" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", @@ -1880,11 +2336,26 @@ "node": ">= 0.8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { "version": "7.5.3", @@ -1906,6 +2377,26 @@ } } }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -1915,6 +2406,99 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "dependencies": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + } + }, + "node_modules/yargonaut/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -1923,6 +2507,20 @@ "engines": { "node": ">=6" } + }, + "node_modules/zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "node_modules/zen-observable-ts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", + "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "dependencies": { + "@types/zen-observable": "0.8.3", + "zen-observable": "0.8.15" + } } }, "dependencies": { @@ -1942,17 +2540,23 @@ "jest": "^27.0.6", "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.10", - "missing-native-js-functions": "^1.2.14", + "missing-native-js-functions": "^1.2.15", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" } }, + "@sqltools/formatter": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" + }, "@types/amqplib": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.8.1.tgz", @@ -2065,6 +2669,11 @@ "@types/node": "*" } }, + "@types/zen-observable": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2098,6 +2707,11 @@ "url-parse": "~1.5.1" } }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2106,6 +2720,11 @@ "color-convert": "^2.0.1" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -2116,12 +2735,22 @@ "picomatch": "^2.0.4" } }, + "app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2136,8 +2765,12 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "binary-extensions": { "version": "2.2.0", @@ -2236,7 +2869,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2257,6 +2889,15 @@ "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", "peer": true }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2303,6 +2944,45 @@ "readdirp": "~3.6.0" } }, + "cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "requires": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "dependencies": { + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2328,8 +3008,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "content-disposition": { "version": "0.5.3", @@ -2435,16 +3114,31 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2505,6 +3199,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2552,8 +3251,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", @@ -2568,11 +3266,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2600,6 +3302,21 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2610,6 +3327,11 @@ "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -2630,11 +3352,15 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2674,6 +3400,11 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -2694,6 +3425,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2846,7 +3585,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2865,8 +3603,7 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mongodb": { "version": "3.6.11", @@ -2925,8 +3662,7 @@ "requires": {} }, "mpath": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", + "version": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==", "peer": true }, @@ -2971,6 +3707,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -2987,6 +3733,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -2999,7 +3750,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -3010,6 +3760,31 @@ "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", "peer": true }, + "parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3018,8 +3793,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { "version": "1.0.7", @@ -3104,12 +3878,22 @@ "picomatch": "^2.2.1" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regexp-clone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==", "peer": true }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3159,6 +3943,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -3207,6 +3996,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "sift": { "version": "13.5.2", "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", @@ -3255,6 +4053,24 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -3275,6 +4091,22 @@ "has-flag": "^4.0.0" } }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3339,6 +4171,11 @@ "strip-json-comments": "^2.0.0" } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3348,6 +4185,45 @@ "mime-types": "~2.1.24" } }, + "typeorm": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.37.tgz", + "integrity": "sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==", + "requires": { + "@sqltools/formatter": "^1.2.2", + "app-root-path": "^3.0.0", + "buffer": "^6.0.3", + "chalk": "^4.1.0", + "cli-highlight": "^2.1.11", + "debug": "^4.3.1", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "mkdirp": "^1.0.4", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.1.0", + "xml2js": "^0.4.23", + "yargonaut": "^1.1.4", + "yargs": "^17.0.1", + "zen-observable-ts": "^1.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", @@ -3397,11 +4273,20 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "7.5.3", @@ -3409,17 +4294,116 @@ "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "requires": {} }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "requires": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "zen-observable-ts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", + "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "requires": { + "@types/zen-observable": "0.8.3", + "zen-observable": "0.8.15" + } } } } diff --git a/gateway/package.json b/gateway/package.json index 310e867e..6b9e27d6 100644 --- a/gateway/package.json +++ b/gateway/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "npm run build && node dist/start.js", + "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js", "build": "npx tsc -b .", "dev": "tsnd --respawn src/start.ts" }, @@ -35,6 +35,7 @@ "missing-native-js-functions": "^1.2.15", "mongoose-autopopulate": "^0.12.3", "node-fetch": "^2.6.1", + "typeorm": "^0.2.37", "uuid": "^8.3.2", "ws": "^7.4.2" } diff --git a/gateway/scripts/tsconfig-paths-bootstrap.js b/gateway/scripts/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..7308523b --- /dev/null +++ b/gateway/scripts/tsconfig-paths-bootstrap.js @@ -0,0 +1,10 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, ".."), + paths: { + "@fosscord/gateway": ["dist/index.js"], + "@fosscord/gateway/*": ["dist/*"], + }, +}); diff --git a/gateway/src/events/Close.ts b/gateway/src/events/Close.ts index 2f274ec4..4afe8352 100644 --- a/gateway/src/events/Close.ts +++ b/gateway/src/events/Close.ts @@ -1,4 +1,4 @@ -import WebSocket from "../util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { Message } from "./Message"; import { Session } from "@fosscord/util"; diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index b3c94eaf..625ceaa2 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -1,10 +1,10 @@ -import WebSocket, { Server } from "../util/WebSocket"; +import WebSocket, { Server } from "@fosscord/gateway/util/WebSocket"; import { IncomingMessage } from "http"; import { Close } from "./Close"; import { Message } from "./Message"; -import { setHeartbeat } from "../util/setHeartbeat"; -import { Send } from "../util/Send"; -import { CLOSECODES, OPCODES } from "../util/Constants"; +import { setHeartbeat } from "@fosscord/gateway/util/setHeartbeat"; +import { Send } from "@fosscord/gateway/util/Send"; +import { CLOSECODES, OPCODES } from "@fosscord/gateway/util/Constants"; import { createDeflate } from "zlib"; import { URL } from "url"; import { Session } from "@fosscord/util"; @@ -17,7 +17,11 @@ try { // TODO: specify rate limit in config // TODO: check msg max size -export async function Connection(this: Server, socket: WebSocket, request: IncomingMessage) { +export async function Connection( + this: Server, + socket: WebSocket, + request: IncomingMessage +) { try { socket.on("close", Close); // @ts-ignore @@ -27,18 +31,21 @@ export async function Connection(this: Server, socket: WebSocket, request: Incom // @ts-ignore socket.encoding = searchParams.get("encoding") || "json"; if (!["json", "etf"].includes(socket.encoding)) { - if (socket.encoding === "etf" && erlpack) throw new Error("Erlpack is not installed: 'npm i -D erlpack'"); + if (socket.encoding === "etf" && erlpack) + throw new Error("Erlpack is not installed: 'npm i -D erlpack'"); return socket.close(CLOSECODES.Decode_error); } // @ts-ignore socket.version = Number(searchParams.get("version")) || 8; - if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); + if (socket.version != 8) + return socket.close(CLOSECODES.Invalid_API_version); // @ts-ignore socket.compress = searchParams.get("compress") || ""; if (socket.compress) { - if (socket.compress !== "zlib-stream") return socket.close(CLOSECODES.Decode_error); + if (socket.compress !== "zlib-stream") + return socket.close(CLOSECODES.Decode_error); socket.deflate = createDeflate({ chunkSize: 65535 }); socket.deflate.on("data", (chunk) => socket.send(chunk)); } diff --git a/gateway/src/events/Message.ts b/gateway/src/events/Message.ts index a8bf5d78..5c6bd2fc 100644 --- a/gateway/src/events/Message.ts +++ b/gateway/src/events/Message.ts @@ -1,10 +1,10 @@ -import WebSocket, { Data } from "../util/WebSocket"; +import WebSocket, { Data } from "@fosscord/gateway/util/WebSocket"; var erlpack: any; try { erlpack = require("erlpack"); } catch (error) {} import OPCodeHandlers from "../opcodes"; -import { Payload, CLOSECODES, OPCODES } from "../util/Constants"; +import { Payload, CLOSECODES, OPCODES } from "@fosscord/gateway/util/Constants"; import { instanceOf, Tuple } from "lambert-server"; import { check } from "../opcodes/instanceOf"; import WS from "ws"; @@ -20,8 +20,10 @@ export async function Message(this: WebSocket, buffer: WS.Data) { // TODO: compression var data: Payload; - if (this.encoding === "etf" && buffer instanceof Buffer) data = erlpack.unpack(buffer); - else if (this.encoding === "json" && typeof buffer === "string") data = JSON.parse(buffer); + if (this.encoding === "etf" && buffer instanceof Buffer) + data = erlpack.unpack(buffer); + else if (this.encoding === "json" && typeof buffer === "string") + data = JSON.parse(buffer); else return; check.call(this, PayloadSchema, data); @@ -41,6 +43,7 @@ export async function Message(this: WebSocket, buffer: WS.Data) { return await OPCodeHandler.call(this, data); } catch (error) { console.error(error); - if (!this.CLOSED && this.CLOSING) return this.close(CLOSECODES.Unknown_error); + if (!this.CLOSED && this.CLOSING) + return this.close(CLOSECODES.Unknown_error); } } diff --git a/gateway/src/index.ts b/gateway/src/index.ts index 7513bd2f..d77ce931 100644 --- a/gateway/src/index.ts +++ b/gateway/src/index.ts @@ -1 +1,4 @@ export * from "./Server"; +export * from "./util/"; +export * from "./opcodes/"; +export * from "./listener/listener"; diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 0b6fa50c..e5630a43 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -9,13 +9,13 @@ import { ListenEventOpts, Member, } from "@fosscord/util"; -import { OPCODES } from "../util/Constants"; -import { Send } from "../util/Send"; -import WebSocket from "../util/WebSocket"; +import { OPCODES } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import "missing-native-js-functions"; import { Channel as AMQChannel } from "amqplib"; -import { In, Like } from "../../../util/node_modules/typeorm"; -import { Recipient } from "../../../util/dist/entities/Recipient"; +import { In, Like } from "typeorm"; +import { Recipient } from "@fosscord/util"; // TODO: close connection on Invalidated Token // TODO: check intent diff --git a/gateway/src/opcodes/Heartbeat.ts b/gateway/src/opcodes/Heartbeat.ts index 015257b9..34d8ca74 100644 --- a/gateway/src/opcodes/Heartbeat.ts +++ b/gateway/src/opcodes/Heartbeat.ts @@ -1,7 +1,7 @@ -import { CLOSECODES, Payload } from "../util/Constants"; -import { Send } from "../util/Send"; -import { setHeartbeat } from "../util/setHeartbeat"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; +import { setHeartbeat } from "@fosscord/gateway/util/setHeartbeat"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export async function onHeartbeat(this: WebSocket, data: Payload) { // TODO: validate payload diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 95db7f3d..04a6c84c 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -1,5 +1,5 @@ -import { CLOSECODES, Payload, OPCODES } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES, Payload, OPCODES } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { Channel, checkToken, @@ -17,12 +17,12 @@ import { } from "@fosscord/util"; import { setupListener } from "../listener/listener"; import { IdentifySchema } from "../schema/Identify"; -import { Send } from "../util/Send"; +import { Send } from "@fosscord/gateway/util/Send"; // import experiments from "./experiments.json"; const experiments: any = []; import { check } from "./instanceOf"; -import { Recipient } from "../../../util/dist/entities/Recipient"; -import { genSessionId } from "../util/SessionUtils"; +import { Recipient } from "@fosscord/util"; +import { genSessionId } from "@fosscord/gateway/util/SessionUtils"; // TODO: bot sharding // TODO: check priviliged intents diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts index b7ee9a96..41ec3446 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/gateway/src/opcodes/LazyRequest.ts @@ -6,9 +6,9 @@ import { Role, } from "@fosscord/util"; import { LazyRequest } from "../schema/LazyRequest"; -import { OPCODES, Payload } from "../util/Constants"; -import { Send } from "../util/Send"; -import WebSocket from "../util/WebSocket"; +import { OPCODES, Payload } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { check } from "./instanceOf"; import "missing-native-js-functions"; diff --git a/gateway/src/opcodes/PresenceUpdate.ts b/gateway/src/opcodes/PresenceUpdate.ts index 3760f8a3..4febbfcb 100644 --- a/gateway/src/opcodes/PresenceUpdate.ts +++ b/gateway/src/opcodes/PresenceUpdate.ts @@ -1,5 +1,5 @@ -import { CLOSECODES, Payload } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export function onPresenceUpdate(this: WebSocket, data: Payload) { // return this.close(CLOSECODES.Unknown_error); diff --git a/gateway/src/opcodes/RequestGuildMembers.ts b/gateway/src/opcodes/RequestGuildMembers.ts index 2701d978..9c1654fa 100644 --- a/gateway/src/opcodes/RequestGuildMembers.ts +++ b/gateway/src/opcodes/RequestGuildMembers.ts @@ -1,6 +1,6 @@ -import { CLOSECODES, Payload } from "../util/Constants"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; -import WebSocket from "../util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export function onRequestGuildMembers(this: WebSocket, data: Payload) { // return this.close(CLOSECODES.Unknown_error); diff --git a/gateway/src/opcodes/Resume.ts b/gateway/src/opcodes/Resume.ts index 4efde9b0..e155c139 100644 --- a/gateway/src/opcodes/Resume.ts +++ b/gateway/src/opcodes/Resume.ts @@ -1,7 +1,7 @@ -import { CLOSECODES, Payload } from "../util/Constants"; -import { Send } from "../util/Send"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; -import WebSocket from "../util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export async function onResume(this: WebSocket, data: Payload) { console.log("Got Resume -> cancel not implemented"); diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/gateway/src/opcodes/VoiceStateUpdate.ts index 95a01608..60803ec3 100644 --- a/gateway/src/opcodes/VoiceStateUpdate.ts +++ b/gateway/src/opcodes/VoiceStateUpdate.ts @@ -1,9 +1,18 @@ import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema"; -import { Payload } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { Payload } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { check } from "./instanceOf"; -import { Config, emitEvent, Guild, Member, Region, VoiceServerUpdateEvent, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; -import { genVoiceToken } from "../util/SessionUtils"; +import { + Config, + emitEvent, + Guild, + Member, + Region, + VoiceServerUpdateEvent, + VoiceState, + VoiceStateUpdateEvent, +} from "@fosscord/util"; +import { genVoiceToken } from "@fosscord/gateway/util/SessionUtils"; // TODO: check if a voice server is setup // Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not. @@ -14,21 +23,27 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { let voiceState: VoiceState; try { voiceState = await VoiceState.findOneOrFail({ - where: { user_id: this.user_id } + where: { user_id: this.user_id }, }); - if (voiceState.session_id !== this.session_id && body.channel_id === null) { + if ( + voiceState.session_id !== this.session_id && + body.channel_id === null + ) { //Should we also check guild_id === null? //changing deaf or mute on a client that's not the one with the same session of the voicestate in the database should be ignored return; } //If a user change voice channel between guild we should send a left event first - if (voiceState.guild_id !== body.guild_id && voiceState.session_id === this.session_id) { + if ( + voiceState.guild_id !== body.guild_id && + voiceState.session_id === this.session_id + ) { await emitEvent({ event: "VOICE_STATE_UPDATE", data: { ...voiceState, channel_id: null }, guild_id: voiceState.guild_id, - }) + }); } //The event send by Discord's client on channel leave has both guild_id and channel_id as null @@ -50,10 +65,11 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { voiceState.member = await Member.findOneOrFail({ where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, relations: ["user", "roles"], - }) + }); //If the session changed we generate a new token - if (voiceState.session_id !== this.session_id) voiceState.token = genVoiceToken(); + if (voiceState.session_id !== this.session_id) + voiceState.token = genVoiceToken(); voiceState.session_id = this.session_id; const { id, ...newObj } = voiceState; @@ -69,13 +85,17 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { //If it's null it means that we are leaving the channel and this event is not needed if (voiceState.channel_id !== null) { - const guild = await Guild.findOne({ id: voiceState.guild_id }) + const guild = await Guild.findOne({ id: voiceState.guild_id }); const regions = Config.get().regions; let guildRegion: Region; if (guild && guild.region) { - guildRegion = regions.available.filter(r => (r.id === guild.region))[0] + guildRegion = regions.available.filter( + (r) => r.id === guild.region + )[0]; } else { - guildRegion = regions.available.filter(r => (r.id === regions.default))[0] + guildRegion = regions.available.filter( + (r) => r.id === regions.default + )[0]; } await emitEvent({ diff --git a/gateway/src/opcodes/index.ts b/gateway/src/opcodes/index.ts index fa57f568..a6d13bfb 100644 --- a/gateway/src/opcodes/index.ts +++ b/gateway/src/opcodes/index.ts @@ -1,5 +1,5 @@ -import { Payload } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { Payload } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { onHeartbeat } from "./Heartbeat"; import { onIdentify } from "./Identify"; import { onLazyRequest } from "./LazyRequest"; diff --git a/gateway/src/opcodes/instanceOf.ts b/gateway/src/opcodes/instanceOf.ts index c4ee5ee6..6d84ac21 100644 --- a/gateway/src/opcodes/instanceOf.ts +++ b/gateway/src/opcodes/instanceOf.ts @@ -1,6 +1,6 @@ import { instanceOf } from "lambert-server"; -import { CLOSECODES } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export function check(this: WebSocket, schema: any, data: any) { try { diff --git a/gateway/src/util/Send.ts b/gateway/src/util/Send.ts index be25ac4f..1b00e361 100644 --- a/gateway/src/util/Send.ts +++ b/gateway/src/util/Send.ts @@ -2,7 +2,7 @@ var erlpack: any; try { erlpack = require("erlpack"); } catch (error) {} -import { Payload } from "../util/Constants"; +import { Payload } from "@fosscord/gateway/util/Constants"; import WebSocket from "./WebSocket"; diff --git a/gateway/src/util/WebSocket.ts b/gateway/src/util/WebSocket.ts index 2c763743..820cb1a3 100644 --- a/gateway/src/util/WebSocket.ts +++ b/gateway/src/util/WebSocket.ts @@ -1,7 +1,6 @@ import { Intents, Permissions } from "@fosscord/util"; -import WS, { Server, Data } from "ws"; +import WS from "ws"; import { Deflate } from "zlib"; -import { Channel } from "amqplib"; interface WebSocket extends WS { version: number; @@ -21,4 +20,3 @@ interface WebSocket extends WS { } export default WebSocket; -export { Server, Data }; diff --git a/gateway/src/util/index.ts b/gateway/src/util/index.ts new file mode 100644 index 00000000..27af5813 --- /dev/null +++ b/gateway/src/util/index.ts @@ -0,0 +1,5 @@ +export * from "./Constants"; +export * from "./Send"; +export * from "./SessionUtils"; +export * from "./setHeartbeat"; +export * from "./WebSocket"; diff --git a/gateway/tsconfig.json b/gateway/tsconfig.json index e5bf92c6..2530aa41 100644 --- a/gateway/tsconfig.json +++ b/gateway/tsconfig.json @@ -67,6 +67,11 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, - "resolveJsonModule": true + "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "@fosscord/gateway/*": ["src/*"], + "@fosscord/util/*": ["../util/src/*"] + } } } diff --git a/util/package-lock.json b/util/package-lock.json index 1f4c1875..97bd66d2 100644 --- a/util/package-lock.json +++ b/util/package-lock.json @@ -23,6 +23,7 @@ "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" @@ -1156,6 +1157,11 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "node_modules/@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -7875,6 +7881,14 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -8042,6 +8056,28 @@ "node": ">=0.8" } }, + "node_modules/tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -9666,6 +9702,11 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -14925,6 +14966,11 @@ "ansi-regex": "^5.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -15051,6 +15097,27 @@ "punycode": "^2.1.1" } }, + "tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + } + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", diff --git a/util/package.json b/util/package.json index 07623f7a..751484af 100644 --- a/util/package.json +++ b/util/package.json @@ -49,6 +49,7 @@ "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37", "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index ae966e42..5c2a01b0 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -423,6 +423,51 @@ export interface RelationshipRemoveEvent extends Event { data: Omit; } +export type EventData = + | InvalidatedEvent + | ReadyEvent + | ChannelCreateEvent + | ChannelUpdateEvent + | ChannelDeleteEvent + | ChannelPinsUpdateEvent + | GuildCreateEvent + | GuildUpdateEvent + | GuildDeleteEvent + | GuildBanAddEvent + | GuildBanRemoveEvent + | GuildEmojiUpdateEvent + | GuildIntegrationUpdateEvent + | GuildMemberAddEvent + | GuildMemberRemoveEvent + | GuildMemberUpdateEvent + | GuildMembersChunkEvent + | GuildRoleCreateEvent + | GuildRoleUpdateEvent + | GuildRoleDeleteEvent + | InviteCreateEvent + | InviteDeleteEvent + | MessageCreateEvent + | MessageUpdateEvent + | MessageDeleteEvent + | MessageDeleteBulkEvent + | MessageReactionAddEvent + | MessageReactionRemoveEvent + | MessageReactionRemoveAllEvent + | MessageReactionRemoveEmojiEvent + | PresenceUpdateEvent + | TypingStartEvent + | UserUpdateEvent + | VoiceStateUpdateEvent + | VoiceServerUpdateEvent + | WebhooksUpdateEvent + | ApplicationCommandCreateEvent + | ApplicationCommandUpdateEvent + | ApplicationCommandDeleteEvent + | InteractionCreateEvent + | MessageAckEvent + | RelationshipAddEvent + | RelationshipRemoveEvent; + // located in collection events export enum EVENTEnum { diff --git a/util/src/util/Constants.ts b/util/src/util/Constants.ts index 713d59da..d2cc5130 100644 --- a/util/src/util/Constants.ts +++ b/util/src/util/Constants.ts @@ -632,7 +632,7 @@ export const DiscordApiErrors = { OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), - MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action", 50013), + MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]), INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), NOTE_TOO_LONG: new ApiError("Note was too long", 50015), INVALID_BULK_DELETE_QUANTITY: new ApiError( diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index d3844cd9..c22d8abd 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -21,7 +21,7 @@ export function initDatabase() { // entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"), synchronize: true, - logging: false, + logging: true, cache: { duration: 1000 * 3, // cache all find queries for 3 seconds }, -- cgit 1.5.1 From e2c66a1fe578ece2c126d554ba04743ecb6991d8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 21:21:08 +0200 Subject: :construction: :sparkles: new body parser (bans route) --- api/assets/schemas.json | 939 ++++++++++++++++++++++++++++++++ api/patches/ajv+8.6.2.patch | 249 +++++++++ api/scripts/generate_body_schema.ts | 59 ++ api/src/routes/guilds/#guild_id/bans.ts | 25 +- api/src/util/route.ts | 66 +++ 5 files changed, 1323 insertions(+), 15 deletions(-) create mode 100644 api/assets/schemas.json create mode 100644 api/patches/ajv+8.6.2.patch create mode 100644 api/scripts/generate_body_schema.ts create mode 100644 api/src/util/route.ts (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json new file mode 100644 index 00000000..b575bf60 --- /dev/null +++ b/api/assets/schemas.json @@ -0,0 +1,939 @@ +{ + "DmChannelCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "recipients" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "ChannelGuildPositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "InviteCreateSchema": { + "type": "object", + "properties": { + "target_user_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "MessageCreateSchema": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embed": { + "additionalProperties": false, + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + } + }, + "allowed_mentions": { + "type": "object", + "properties": { + "parse": { + "type": "array", + "items": { + "type": "string" + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + }, + "replied_user": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "message_reference": { + "type": "object", + "properties": { + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "fail_if_not_exists": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "message_id" + ] + }, + "payload_json": { + "type": "string" + }, + "file": {} + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "BanCreateSchema": { + "type": "object", + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/ChannelModifySchema" + } + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateSchema": { + "type": "object", + "properties": { + "banner": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "description": { + "type": "string" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildTemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateWelcomeScreenSchema": { + "type": "object", + "properties": { + "welcome_channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "description", + "emoji_name" + ] + } + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberCreateSchema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "nick": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "joined_at": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "guild_id", + "id", + "joined_at", + "nick" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "nick" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RoleModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "permissions": { + "type": "bigint" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id", + "position" + ] + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "EmojiCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "image", + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserModifySchema": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserSettingsSchema": { + "type": "object", + "properties": { + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, + "custom_status": { + "type": "object", + "properties": { + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "additionalProperties": false + }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, + "friend_source_flags": { + "type": "object", + "properties": { + "all": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "all" + ] + }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, + "guild_folders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "color", + "guild_ids", + "id", + "name" + ] + } + }, + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": [ + "dnd", + "idle", + "offline", + "online" + ], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": [ + "dark", + "white" + ], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "afk_timeout", + "allow_accessibility_detection", + "animate_emoji", + "animate_stickers", + "contact_sync_enabled", + "convert_emoticons", + "custom_status", + "default_guilds_restricted", + "detect_platform_accounts", + "developer_mode", + "disable_games_tab", + "enable_tts_command", + "explicit_content_filter", + "friend_source_flags", + "gateway_connected", + "gif_auto_play", + "guild_folders", + "guild_positions", + "inline_attachment_media", + "inline_embed_media", + "locale", + "message_display_compact", + "native_phone_integration_enabled", + "render_embeds", + "render_reactions", + "restricted_guilds", + "show_current_game", + "status", + "stream_notifications_enabled", + "theme", + "timezone_offset" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WidgetModifySchema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "enabled" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + } +} \ No newline at end of file diff --git a/api/patches/ajv+8.6.2.patch b/api/patches/ajv+8.6.2.patch new file mode 100644 index 00000000..3f54881b --- /dev/null +++ b/api/patches/ajv+8.6.2.patch @@ -0,0 +1,249 @@ +diff --git a/node_modules/ajv/dist/compile/jtd/parse.js b/node_modules/ajv/dist/compile/jtd/parse.js +index 1eeb1be..7684121 100644 +--- a/node_modules/ajv/dist/compile/jtd/parse.js ++++ b/node_modules/ajv/dist/compile/jtd/parse.js +@@ -239,6 +239,9 @@ function parseType(cxt) { + gen.if(fail, () => parsingError(cxt, codegen_1.str `invalid timestamp`)); + break; + } ++ case "bigint": ++ parseBigInt(cxt); ++ break + case "float32": + case "float64": + parseNumber(cxt); +@@ -284,6 +287,15 @@ function parseNumber(cxt, maxDigits) { + skipWhitespace(cxt); + gen.if(codegen_1._ `"-0123456789".indexOf(${jsonSlice(1)}) < 0`, () => jsonSyntaxError(cxt), () => parseWith(cxt, parseJson_1.parseJsonNumber, maxDigits)); + } ++function parseBigInt(cxt, maxDigits) { ++ const {gen} = cxt ++ skipWhitespace(cxt) ++ gen.if( ++ _`"-0123456789".indexOf(${jsonSlice(1)}) < 0`, ++ () => jsonSyntaxError(cxt), ++ () => parseWith(cxt, parseJson_1.parseJsonBigInt, maxDigits) ++ ) ++} + function parseBooleanToken(bool, fail) { + return (cxt) => { + const { gen, data } = cxt; +diff --git a/node_modules/ajv/dist/compile/rules.js b/node_modules/ajv/dist/compile/rules.js +index 82a591f..1ebd8fe 100644 +--- a/node_modules/ajv/dist/compile/rules.js ++++ b/node_modules/ajv/dist/compile/rules.js +@@ -1,7 +1,7 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getRules = exports.isJSONType = void 0; +-const _jsonTypes = ["string", "number", "integer", "boolean", "null", "object", "array"]; ++const _jsonTypes = ["string", "number", "integer", "boolean", "null", "object", "array","bigint"]; + const jsonTypes = new Set(_jsonTypes); + function isJSONType(x) { + return typeof x == "string" && jsonTypes.has(x); +@@ -13,10 +13,11 @@ function getRules() { + string: { type: "string", rules: [] }, + array: { type: "array", rules: [] }, + object: { type: "object", rules: [] }, ++ bigint: {type: "bigint", rules: []} + }; + return { +- types: { ...groups, integer: true, boolean: true, null: true }, +- rules: [{ rules: [] }, groups.number, groups.string, groups.array, groups.object], ++ types: { ...groups, integer: true, boolean: true, null: true, bigint: true }, ++ rules: [{ rules: [] }, groups.number, groups.string, groups.array, groups.object, groups.bigint], + post: { rules: [] }, + all: {}, + keywords: {}, +diff --git a/node_modules/ajv/dist/compile/validate/dataType.js b/node_modules/ajv/dist/compile/validate/dataType.js +index 6319e76..8b50b4c 100644 +--- a/node_modules/ajv/dist/compile/validate/dataType.js ++++ b/node_modules/ajv/dist/compile/validate/dataType.js +@@ -52,7 +52,7 @@ function coerceAndCheckDataType(it, types) { + return checkTypes; + } + exports.coerceAndCheckDataType = coerceAndCheckDataType; +-const COERCIBLE = new Set(["string", "number", "integer", "boolean", "null"]); ++const COERCIBLE = new Set(["string", "number", "integer", "boolean", "null","bigint"]); + function coerceToTypes(types, coerceTypes) { + return coerceTypes + ? types.filter((t) => COERCIBLE.has(t) || (coerceTypes === "array" && t === "array")) +@@ -83,6 +83,14 @@ function coerceData(it, types, coerceTo) { + }); + function coerceSpecificType(t) { + switch (t) { ++ case "bigint": ++ gen ++ .elseIf( ++ codegen_1._`${dataType} == "boolean" || ${data} === null ++ || (${dataType} == "string" && ${data} && ${data} == BigInt(${data}))` ++ ) ++ .assign(coerced, codegen_1._`BigInt(${data})`) ++ return + case "string": + gen + .elseIf(codegen_1._ `${dataType} == "number" || ${dataType} == "boolean"`) +@@ -143,6 +151,9 @@ function checkDataType(dataType, data, strictNums, correct = DataType.Correct) { + case "number": + cond = numCond(); + break; ++ case "bigint": ++ cond = codegen_1._`typeof ${data} == "bigint" && isFinite(${data})` ++ break + default: + return codegen_1._ `typeof ${data} ${EQ} ${dataType}`; + } +diff --git a/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json b/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json +index 7027a12..25679c8 100644 +--- a/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json ++++ b/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json +@@ -78,7 +78,7 @@ + "default": 0 + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json b/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json +index e0ae13d..57c9036 100644 +--- a/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json ++++ b/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json +@@ -78,7 +78,7 @@ + "default": 0 + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/json-schema-draft-06.json b/node_modules/ajv/dist/refs/json-schema-draft-06.json +index 5410064..774435b 100644 +--- a/node_modules/ajv/dist/refs/json-schema-draft-06.json ++++ b/node_modules/ajv/dist/refs/json-schema-draft-06.json +@@ -16,7 +16,7 @@ + "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/json-schema-draft-07.json b/node_modules/ajv/dist/refs/json-schema-draft-07.json +index 6a74851..fc6dd7d 100644 +--- a/node_modules/ajv/dist/refs/json-schema-draft-07.json ++++ b/node_modules/ajv/dist/refs/json-schema-draft-07.json +@@ -16,7 +16,7 @@ + "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/jtd-schema.js b/node_modules/ajv/dist/refs/jtd-schema.js +index 1ee940a..1148887 100644 +--- a/node_modules/ajv/dist/refs/jtd-schema.js ++++ b/node_modules/ajv/dist/refs/jtd-schema.js +@@ -38,6 +38,7 @@ const typeForm = (root) => ({ + "uint16", + "int32", + "uint32", ++ "bigint", + ], + }, + }, +diff --git a/node_modules/ajv/dist/runtime/parseJson.js b/node_modules/ajv/dist/runtime/parseJson.js +index 2576a6e..e7447b1 100644 +--- a/node_modules/ajv/dist/runtime/parseJson.js ++++ b/node_modules/ajv/dist/runtime/parseJson.js +@@ -97,6 +97,71 @@ exports.parseJsonNumber = parseJsonNumber; + parseJsonNumber.message = undefined; + parseJsonNumber.position = 0; + parseJsonNumber.code = 'require("ajv/dist/runtime/parseJson").parseJsonNumber'; ++ ++function parseJsonBigInt(s, pos, maxDigits) { ++ let numStr = ""; ++ let c; ++ parseJsonBigInt.message = undefined; ++ if (s[pos] === "-") { ++ numStr += "-"; ++ pos++; ++ } ++ if (s[pos] === "0") { ++ numStr += "0"; ++ pos++; ++ } ++ else { ++ if (!parseDigits(maxDigits)) { ++ errorMessage(); ++ return undefined; ++ } ++ } ++ if (maxDigits) { ++ parseJsonBigInt.position = pos; ++ return BigInt(numStr); ++ } ++ if (s[pos] === ".") { ++ numStr += "."; ++ pos++; ++ if (!parseDigits()) { ++ errorMessage(); ++ return undefined; ++ } ++ } ++ if (((c = s[pos]), c === "e" || c === "E")) { ++ numStr += "e"; ++ pos++; ++ if (((c = s[pos]), c === "+" || c === "-")) { ++ numStr += c; ++ pos++; ++ } ++ if (!parseDigits()) { ++ errorMessage(); ++ return undefined; ++ } ++ } ++ parseJsonBigInt.position = pos; ++ return BigInt(numStr); ++ function parseDigits(maxLen) { ++ let digit = false; ++ while (((c = s[pos]), c >= "0" && c <= "9" && (maxLen === undefined || maxLen-- > 0))) { ++ digit = true; ++ numStr += c; ++ pos++; ++ } ++ return digit; ++ } ++ function errorMessage() { ++ parseJsonBigInt.position = pos; ++ parseJsonBigInt.message = pos < s.length ? `unexpected token ${s[pos]}` : "unexpected end"; ++ } ++} ++exports.parseJsonBigInt = parseJsonBigInt; ++parseJsonBigInt.message = undefined; ++parseJsonBigInt.position = 0; ++parseJsonBigInt.code = 'require("ajv/dist/runtime/parseJson").parseJsonBigInt'; ++ ++ + const escapedChars = { + b: "\b", + f: "\f", +diff --git a/node_modules/ajv/dist/vocabularies/jtd/type.js b/node_modules/ajv/dist/vocabularies/jtd/type.js +index 428bddb..fbc3070 100644 +--- a/node_modules/ajv/dist/vocabularies/jtd/type.js ++++ b/node_modules/ajv/dist/vocabularies/jtd/type.js +@@ -45,6 +45,9 @@ const def = { + cond = timestampCode(cxt); + break; + } ++ case "bigint": ++ cond = codegen_1._`typeof ${data} == "bigint" || typeof ${data} == "string"` ++ break + case "float32": + case "float64": + cond = codegen_1._ `typeof ${data} == "number"`; \ No newline at end of file diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts new file mode 100644 index 00000000..69e62085 --- /dev/null +++ b/api/scripts/generate_body_schema.ts @@ -0,0 +1,59 @@ +// 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 schemaPath = path.join(__dirname, "..", "assets", "schemas.json"); + +const settings: TJS.PartialArgs = { + required: true, + ignoreErrors: true, + excludePrivate: true, + defaultNumberType: "integer", + noExtraProps: true, + defaultProps: false +}; +const compilerOptions: TJS.CompilerOptions = { + strictNullChecks: false +}; +const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"]; + +function main() { + const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); + const generator = TJS.buildGenerator(program, settings); + if (!generator || !program) return; + + const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)); + console.log(schemas); + + var definitions: any = {}; + + for (const name of schemas) { + const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); + if (!part) continue; + + definitions = { ...definitions, ...part.definitions, [name]: { ...part, definitions: undefined } }; + } + + fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); +} + +main(); + +function walk(dir: string) { + var results = [] as string[]; + var list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + "/" + file; + var stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file)); + } else { + if (!file.endsWith(".ts")) return; + results.push(file); + } + }); + return results; +} diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 86bff6b4..41a97b6a 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,19 +1,18 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { getIpAdress, check } from "@fosscord/api"; +import { getIpAdress, check, route } from "@fosscord/api"; import { BanCreateSchema } from "@fosscord/api/schema/Ban"; const router: Router = Router(); - -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; var bans = await Ban.find({ guild_id: guild_id }); return res.json(bans); }); -router.get("/:user", async (req: Request, res: Response) => { +router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const user_id = req.params.ban; @@ -21,15 +20,14 @@ router.get("/:user", async (req: Request, res: Response) => { return res.json(ban); }); -router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Response) => { +router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const banned_user_id = req.params.user_id; const banned_user = await User.getPublicUser(banned_user_id); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("BAN_MEMBERS"); + if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); - if (perms.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); const ban = new Ban({ user_id: banned_user_id, @@ -55,17 +53,14 @@ router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Respon return res.json(ban); }); -router.delete("/:user_id", async (req: Request, res: Response) => { - var { guild_id } = req.params; - var banned_user_id = req.params.user_id; +router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - const banned_user = await User.getPublicUser(banned_user_id); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("BAN_MEMBERS"); + const banned_user = await User.getPublicUser(user_id); await Promise.all([ Ban.delete({ - user_id: banned_user_id, + user_id: user_id, guild_id }), diff --git a/api/src/util/route.ts b/api/src/util/route.ts new file mode 100644 index 00000000..0302f3ec --- /dev/null +++ b/api/src/util/route.ts @@ -0,0 +1,66 @@ +import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { NextFunction, Request, Response } from "express"; +import fs from "fs"; +import path from "path"; +import Ajv from "ajv"; +import { AnyValidateFunction } from "ajv/dist/core"; +import { FieldErrors } from ".."; + +const SchemaPath = path.join(__dirname, "..", "..", "assets", "schemas.json"); +const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); +export const ajv = new Ajv({ allErrors: true, parseDate: true, allowDate: true, schemas, messages: true }); + +declare global { + namespace Express { + interface Request { + permission?: Permissions; + } + } +} + +export type RouteSchema = string; // typescript interface name +export type RouteResponse = { status: number; body?: RouteSchema; headers?: Record }; + +export interface RouteOptions { + permission?: PermissionResolvable; + body?: RouteSchema; + response?: RouteResponse; + example?: { + body?: any; + path?: string; + event?: EventData; + headers?: Record; + }; +} + +export function route(opts: RouteOptions) { + var validate: AnyValidateFunction; + if (opts.body) { + // @ts-ignore + validate = ajv.getSchema(opts.body); + if (!validate) throw new Error(`Body schema ${opts.body} not found`); + } + + return async (req: Request, res: Response, next: NextFunction) => { + if (opts.permission) { + const required = new Permissions(opts.permission); + const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); + console.log(required.bitfield, permission.bitfield, permission.bitfield & required.bitfield); + + // bitfield comparison: check if user lacks certain permission + if (!permission.has(required)) { + throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); + } + + if (validate) { + const valid = validate(req.body); + if (!valid) { + const fields: Record = {}; + validate.errors?.forEach((x) => (fields[x.instancePath] = { code: x.keyword, message: x.message || "" })); + throw FieldErrors(fields); + } + } + } + next(); + }; +} -- cgit 1.5.1 From 6ba9c834bd2d5f4fac07e3fb9ded2625ce3c44ad Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 23:28:56 +0200 Subject: :sparkles: #307 done --- api/assets/schemas.json | 7359 ++++++++++++++++++-- api/package-lock.json | 2 +- api/package.json | 6 +- api/scripts/generate_body_schema.ts | 3 +- api/src/routes/auth/login.ts | 108 +- api/src/routes/auth/register.ts | 357 +- api/src/routes/channels/#channel_id/index.ts | 49 +- api/src/routes/channels/#channel_id/invites.ts | 40 +- .../#channel_id/messages/#message_id/ack.ts | 10 +- .../#channel_id/messages/#message_id/index.ts | 16 +- .../#channel_id/messages/#message_id/reactions.ts | 33 +- .../channels/#channel_id/messages/bulk-delete.ts | 9 +- .../routes/channels/#channel_id/messages/index.ts | 101 +- api/src/routes/channels/#channel_id/permissions.ts | 82 +- api/src/routes/channels/#channel_id/pins.ts | 33 +- api/src/routes/channels/#channel_id/typing.ts | 6 +- api/src/routes/channels/#channel_id/webhooks.ts | 16 +- api/src/routes/discoverable-guilds.ts | 4 +- api/src/routes/experiments.ts | 3 +- api/src/routes/gateway.ts | 3 +- api/src/routes/guilds/#guild_id/bans.ts | 8 +- api/src/routes/guilds/#guild_id/channels.ts | 74 +- api/src/routes/guilds/#guild_id/delete.ts | 3 +- api/src/routes/guilds/#guild_id/index.ts | 34 +- api/src/routes/guilds/#guild_id/invites.ts | 6 +- .../guilds/#guild_id/members/#member_id/index.ts | 36 +- .../guilds/#guild_id/members/#member_id/nick.ts | 9 +- .../members/#member_id/roles/#role_id/index.ts | 11 +- api/src/routes/guilds/#guild_id/members/index.ts | 6 +- api/src/routes/guilds/#guild_id/regions.ts | 4 +- api/src/routes/guilds/#guild_id/roles.ts | 44 +- api/src/routes/guilds/#guild_id/templates.ts | 51 +- api/src/routes/guilds/#guild_id/vanity-url.ts | 24 +- .../#guild_id/voice-states/#user_id/index.ts | 57 +- .../guilds/#guild_id/voice-states/@me/index.ts | 15 - api/src/routes/guilds/#guild_id/welcome_screen.ts | 23 +- api/src/routes/guilds/#guild_id/widget.json.ts | 4 +- api/src/routes/guilds/#guild_id/widget.png.ts | 3 +- api/src/routes/guilds/#guild_id/widget.ts | 18 +- api/src/routes/guilds/index.ts | 19 +- api/src/routes/guilds/templates/index.ts | 12 +- api/src/routes/invites/index.ts | 8 +- api/src/routes/ping.ts | 3 +- api/src/routes/science.ts | 3 +- api/src/routes/users/#id/index.ts | 3 +- api/src/routes/users/#id/profile.ts | 17 +- api/src/routes/users/@me/affinities/guilds.ts | 3 +- api/src/routes/users/@me/affinities/user.ts | 3 +- api/src/routes/users/@me/channels.ts | 12 +- api/src/routes/users/@me/delete.ts | 4 +- api/src/routes/users/@me/devices.ts | 3 +- api/src/routes/users/@me/disable.ts | 3 +- api/src/routes/users/@me/guilds.ts | 6 +- api/src/routes/users/@me/index.ts | 25 +- api/src/routes/users/@me/library.ts | 3 +- api/src/routes/users/@me/relationships.ts | 150 +- api/src/routes/users/@me/settings.ts | 7 +- api/src/routes/voice/regions.ts | 4 +- api/src/schema/Ban.ts | 9 - api/src/schema/Channel.ts | 68 - api/src/schema/Emoji.ts | 13 - api/src/schema/Guild.ts | 106 - api/src/schema/Invite.ts | 22 - api/src/schema/Member.ts | 29 - api/src/schema/Message.ts | 92 - api/src/schema/Roles.ts | 29 - api/src/schema/Template.ts | 19 - api/src/schema/User.ts | 74 - api/src/schema/Widget.ts | 10 - api/src/schema/index.ts | 11 - api/src/util/Message.ts | 2 +- api/src/util/Voice.ts | 50 +- api/src/util/VoiceState.ts | 54 - api/src/util/index.ts | 1 - api/src/util/route.ts | 14 +- 75 files changed, 7857 insertions(+), 1704 deletions(-) delete mode 100644 api/src/routes/guilds/#guild_id/voice-states/@me/index.ts delete mode 100644 api/src/schema/Ban.ts delete mode 100644 api/src/schema/Channel.ts delete mode 100644 api/src/schema/Emoji.ts delete mode 100644 api/src/schema/Guild.ts delete mode 100644 api/src/schema/Invite.ts delete mode 100644 api/src/schema/Member.ts delete mode 100644 api/src/schema/Message.ts delete mode 100644 api/src/schema/Roles.ts delete mode 100644 api/src/schema/Template.ts delete mode 100644 api/src/schema/User.ts delete mode 100644 api/src/schema/Widget.ts delete mode 100644 api/src/schema/index.ts delete mode 100644 api/src/util/VoiceState.ts (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index b575bf60..59ab4c04 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1,20 +1,75 @@ { - "DmChannelCreateSchema": { + "RegisterSchema": { "type": "object", "properties": { - "name": { + "username": { + "minLength": 2, + "maxLength": 32, "type": "string" }, - "recipients": { - "type": "array", - "items": { - "type": "string" - } + "password": { + "minLength": 1, + "maxLength": 72, + "type": "string" + }, + "consent": { + "type": "boolean" + }, + "email": { + "format": "email", + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "invite": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date-time" + }, + "gift_code_sku_id": { + "type": "string" + }, + "captcha_key": { + "type": "string" } }, "additionalProperties": false, "required": [ - "recipients" + "consent", + "password", + "username" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "LoginSchema": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "undelete": { + "type": "boolean" + }, + "captcha_key": { + "type": "string" + }, + "login_source": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "login", + "password" ], "$schema": "http://json-schema.org/draft-07/schema#" }, @@ -22,10 +77,11 @@ "type": "object", "properties": { "name": { + "maxLength": 100, "type": "string" }, "type": { - "type": "integer" + "$ref": "#/definitions/ChannelType" }, "topic": { "type": "string" @@ -51,7 +107,7 @@ "type": "string" }, "type": { - "type": "integer" + "$ref": "#/definitions/ChannelPermissionOverwriteType" }, "allow": { "type": "bigint" @@ -89,107 +145,32 @@ "required": [ "name", "type" - ] - }, - "ChannelGuildPositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "id" - ] - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "InviteCreateSchema": { - "type": "object", - "properties": { - "target_user_id": { - "type": "string" - }, - "target_type": { - "type": "string" - }, - "validate": { - "type": "string" - }, - "max_age": { - "type": "integer" - }, - "max_uses": { - "type": "integer" - }, - "temporary": { - "type": "boolean" - }, - "unique": { - "type": "boolean" - }, - "target_user": { - "type": "string" - }, - "target_user_type": { - "type": "integer" - } - }, - "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" ], - "type": "string" - }, - "EmbedImage": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "proxy_url": { - "type": "string" - }, - "height": { - "type": "integer" - }, - "width": { - "type": "integer" - } - }, - "additionalProperties": false - }, - "MessageCreateSchema": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "nonce": { - "type": "string" - }, - "tts": { - "type": "boolean" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "flags": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" }, - "embed": { - "additionalProperties": false, + "Embed": { "type": "object", "properties": { "title": { @@ -205,7 +186,8 @@ "type": "string" }, "timestamp": { - "type": "string" + "type": "string", + "format": "date-time" }, "color": { "type": "integer" @@ -289,8 +271,148 @@ ] } } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MessageCreateSchema": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/definitions/Embed" } }, + "embed": { + "$ref": "#/definitions/Embed" + }, "allowed_mentions": { "type": "object", "properties": { @@ -346,377 +468,6580 @@ "file": {} }, "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "BanCreateSchema": { - "type": "object", - "properties": { - "delete_message_days": { - "type": "string" - }, - "reason": { - "type": "string" - } - }, - "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "GuildCreateSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "region": { - "type": "string" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "icon": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" }, - "channels": { - "type": "array", - "items": { - "$ref": "#/definitions/ChannelModifySchema" - } + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false }, - "guild_template_code": { + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], "type": "string" }, - "system_channel_id": { - "type": "string" + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false }, - "rules_channel_id": { - "type": "string" + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, - "required": [ - "name" - ], "$schema": "http://json-schema.org/draft-07/schema#" }, - "GuildUpdateSchema": { + "InviteCreateSchema": { "type": "object", "properties": { - "banner": { + "target_user_id": { "type": "string" }, - "splash": { + "target_type": { "type": "string" }, - "description": { + "validate": { "type": "string" }, - "features": { - "type": "array", - "items": { - "type": "string" - } - }, - "verification_level": { + "max_age": { "type": "integer" }, - "default_message_notifications": { + "max_uses": { "type": "integer" }, - "system_channel_flags": { - "type": "integer" + "temporary": { + "type": "boolean" }, - "explicit_content_filter": { - "type": "integer" + "unique": { + "type": "boolean" }, - "public_updates_channel_id": { + "target_user": { "type": "string" }, - "afk_timeout": { + "target_user_type": { "type": "integer" - }, - "afk_channel_id": { - "type": "string" - }, - "preferred_locale": { - "type": "string" - }, - "name": { - "type": "string" - }, - "region": { - "type": "string" - }, - "icon": { - "type": "string" - }, - "guild_template_code": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "rules_channel_id": { - "type": "string" } }, "additionalProperties": false, - "required": [ - "name" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "GuildTemplateCreateSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "avatar": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "name" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "GuildUpdateWelcomeScreenSchema": { - "type": "object", - "properties": { - "welcome_channels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } }, - "description": { - "type": "string" + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } }, - "emoji_id": { - "type": "string" + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } }, - "emoji_name": { - "type": "string" + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" }, - "additionalProperties": false, - "required": [ - "channel_id", - "description", - "emoji_name" - ] - } + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false }, - "enabled": { - "type": "boolean" + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] }, - "description": { - "type": "string" + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" }, - "VoiceStateUpdateSchema": { + "MessageAcknowledgeSchema": { "type": "object", "properties": { - "channel_id": { - "type": "string" - }, - "suppress": { + "manual": { "type": "boolean" }, - "request_to_speak_timestamp": { - "type": "string" + "mention_count": { + "type": "integer" } }, "additionalProperties": false, - "required": [ - "channel_id" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "MemberCreateSchema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "nick": { - "type": "string" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "guild_id": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" }, - "joined_at": { - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false, - "required": [ - "guild_id", - "id", - "joined_at", - "nick" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "MemberNickChangeSchema": { - "type": "object", - "properties": { - "nick": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "nick" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "MemberChangeSchema": { - "type": "object", - "properties": { - "roles": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "RoleModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "permissions": { - "type": "bigint" + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false }, - "color": { - "type": "integer" + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, - "hoist": { - "type": "boolean" + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false }, - "mentionable": { - "type": "boolean" + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] }, - "position": { - "type": "integer" + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" }, - "RolePositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { + "BulkDeleteSchema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "messages" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelPermissionOverwriteSchema": { + "type": "object", + "properties": { + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WebhookCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 80, + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "avatar", + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "BanCreateSchema": { + "type": "object", + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelReorderSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "lock_permissions": { + "type": "boolean" + }, + "parent_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + }, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/ChannelModifySchema" + } + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateSchema": { + "type": "object", + "properties": { + "banner": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "description": { + "type": "string" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "nick" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RoleModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "permissions": { + "type": "bigint" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id", + "position" + ] + }, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "VanityUrlSchema": { + "type": "object", + "properties": { + "code": { + "minLength": 1, + "maxLength": 20, + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string", + "format": "date-time" + }, + "self_mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "channel_id" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateWelcomeScreenSchema": { + "type": "object", + "properties": { + "welcome_channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "description", + "emoji_name" + ] + } + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WidgetModifySchema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "enabled" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildTemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "DmChannelCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "recipients" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserModifySchema": { + "type": "object", + "properties": { + "username": { + "minLength": 1, + "maxLength": 100, + "type": "string" + }, + "avatar": { + "type": "string" + }, + "bio": { + "maxLength": 1024, + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RelationshipPutSchema": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/RelationshipType" + } + }, + "additionalProperties": false, + "required": [ + "type" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } }, - "position": { - "type": "integer" - } + "additionalProperties": false }, - "additionalProperties": false, - "required": [ - "id", - "position" - ] - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "TemplateCreateSchema": { - "type": "object", - "properties": { - "name": { + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], "type": "string" }, - "description": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "name" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "TemplateModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false }, - "description": { - "type": "string" + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, - "required": [ - "name" - ], "$schema": "http://json-schema.org/draft-07/schema#" }, - "EmojiCreateSchema": { + "RelationshipPostSchema": { "type": "object", "properties": { - "name": { + "discriminator": { "type": "string" }, - "image": { + "username": { "type": "string" - }, - "roles": { - "type": "array", - "items": { - "type": "string" - } } }, "additionalProperties": false, "required": [ - "image", - "name" + "discriminator", + "username" ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "UserModifySchema": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "avatar": { - "type": "string" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "bio": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" }, - "accent_color": { - "type": "integer" + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false }, - "banner": { + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], "type": "string" }, - "password": { - "type": "string" + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false }, - "new_password": { - "type": "string" + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] }, - "code": { - "type": "string" + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" }, "UserSettingsSchema": { @@ -917,23 +7242,247 @@ "theme", "timezone_offset" ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "WidgetModifySchema": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "channel_id": { + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, - "required": [ - "channel_id", - "enabled" - ], "$schema": "http://json-schema.org/draft-07/schema#" } } \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 8b2c93f8..c14b92fa 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@fosscord/util": "file:../util", "ajv": "^8.4.0", - "ajv-formats": "^2.1.0", + "ajv-formats": "^2.1.1", "amqplib": "^0.8.0", "assert": "^1.5.0", "atomically": "^1.7.0", diff --git a/api/package.json b/api/package.json index b762b944..e7b476c7 100644 --- a/api/package.json +++ b/api/package.json @@ -12,7 +12,9 @@ "build-docker": "tsc -p tsconfig-docker.json", "dev": "tsnd --respawn src/start.ts", "patch": "npx patch-package", - "postinstall": "npm run patch" + "postinstall": "npm run patch", + "generate:docs": "ts-node scripts/generate_openapi_schema.ts", + "generate:schema": "ts-node scripts/generate_body_schema.ts" }, "repository": { "type": "git", @@ -60,7 +62,7 @@ "dependencies": { "@fosscord/util": "file:../util", "ajv": "^8.4.0", - "ajv-formats": "^2.1.0", + "ajv-formats": "^2.1.1", "amqplib": "^0.8.0", "assert": "^1.5.0", "atomically": "^1.7.0", diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts index 69e62085..c406cab8 100644 --- a/api/scripts/generate_body_schema.ts +++ b/api/scripts/generate_body_schema.ts @@ -33,12 +33,13 @@ function main() { const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); if (!part) continue; - definitions = { ...definitions, ...part.definitions, [name]: { ...part, definitions: undefined } }; + definitions = { ...definitions, [name]: { ...part } }; } fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); } +// #/definitions/ main(); function walk(dir: string) { diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index 2e2f763d..f672658a 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { check, FieldErrors, Length } from "@fosscord/api"; +import { FieldErrors, route } from "@fosscord/api"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Config, User } from "@fosscord/util"; @@ -8,67 +8,65 @@ import { adjustEmail } from "./register"; const router: Router = Router(); export default router; -router.post( - "/", - check({ - login: new Length(String, 2, 100), // email or telephone - password: new Length(String, 8, 72), - $undelete: Boolean, - $captcha_key: String, - $login_source: String, - $gift_code_sku_id: String - }), - async (req: Request, res: Response) => { - const { login, password, captcha_key, undelete } = req.body; - const email = adjustEmail(login); - console.log("login", email); - - const config = Config.get(); - - if (config.login.requireCaptcha && config.security.captcha.enabled) { - if (!captcha_key) { - const { sitekey, service } = config.security.captcha; - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service - }); - } - - // TODO: check captcha - } +export interface LoginSchema { + login: string; + password: string; + undelete?: boolean; + captcha_key?: string; + login_source?: string; + gift_code_sku_id?: string; +} - const user = await User.findOneOrFail({ - where: [{ phone: login }, { email: login }], - select: ["data", "id", "disabled", "deleted", "settings"] - }).catch((e) => { - throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); - }); - - if (undelete) { - // undelete refers to un'disable' here - if (user.disabled) await User.update({ id: user.id }, { disabled: false }); - if (user.deleted) await User.update({ id: user.id }, { deleted: false }); - } else { - if (user.deleted) return res.status(400).json({ message: "This account is scheduled for deletion.", code: 20011 }); - if (user.disabled) return res.status(400).json({ message: req.t("auth:login.ACCOUNT_DISABLED"), code: 20013 }); +router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Response) => { + const { login, password, captcha_key, undelete } = req.body as LoginSchema; + const email = adjustEmail(login); + console.log("login", email); + + const config = Config.get(); + + if (config.login.requireCaptcha && config.security.captcha.enabled) { + if (!captcha_key) { + const { sitekey, service } = config.security.captcha; + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service + }); } - // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare(password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); - } + // TODO: check captcha + } - const token = await generateToken(user.id); + const user = await User.findOneOrFail({ + where: [{ phone: login }, { email: login }], + select: ["data", "id", "disabled", "deleted", "settings"] + }).catch((e) => { + throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); + }); - // Notice this will have a different token structure, than discord - // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package - // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + if (undelete) { + // undelete refers to un'disable' here + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); + } else { + if (user.deleted) return res.status(400).json({ message: "This account is scheduled for deletion.", code: 20011 }); + if (user.disabled) return res.status(400).json({ message: req.t("auth:login.ACCOUNT_DISABLED"), code: 20013 }); + } - res.json({ token, settings: user.settings }); + // the salt is saved in the password refer to bcrypt docs + const same_password = await bcrypt.compare(password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); } -); + + const token = await generateToken(user.id); + + // Notice this will have a different token structure, than discord + // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package + // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + + res.json({ token, settings: user.settings }); +}); export async function generateToken(id: string) { const iat = Math.floor(Date.now() / 1000); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index d3c85778..e0af1d6d 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; import bcrypt from "bcrypt"; -import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "@fosscord/api"; +import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api"; import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; @@ -10,208 +10,215 @@ import { In } from "typeorm"; const router: Router = Router(); -router.post( - "/", - check({ - username: new Length(String, 2, 32), - // TODO: check min password length in config - // prevent Denial of Service with max length of 72 chars - password: new Length(String, 8, 72), - consent: Boolean, - $email: new Length(Email, 5, 100), - $fingerprint: String, - $invite: String, - $date_of_birth: Date, // "2000-04-03" - $gift_code_sku_id: String, - $captcha_key: String - }), - async (req: Request, res: Response) => { - const { - email, - username, - password, - consent, - fingerprint, - invite, - date_of_birth, - gift_code_sku_id, // ? what is this - captcha_key - } = req.body; - - // get register Config - const { register, security } = Config.get(); - const ip = getIpAdress(req); - - if (register.blockProxies) { - if (isProxy(await IPAnalysis(ip))) { - console.log(`proxy ${ip} blocked from registration`); - throw new HTTPError("Your IP is blocked from registration"); - } +export interface RegisterSchema { + /** + * @minLength 2 + * @maxLength 32 + */ + username: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; // TODO: use password strength of config + consent: boolean; + /** + * @TJS-format email + */ + email?: string; + fingerprint?: string; + invite?: string; + date_of_birth?: Date; // "2000-04-03" + gift_code_sku_id?: string; + captcha_key?: string; +} + +router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => { + const { + email, + username, + password, + consent, + fingerprint, + invite, + date_of_birth, + gift_code_sku_id, // ? what is this + captcha_key + } = req.body; + + // get register Config + const { register, security } = Config.get(); + const ip = getIpAdress(req); + + if (register.blockProxies) { + if (isProxy(await IPAnalysis(ip))) { + console.log(`proxy ${ip} blocked from registration`); + throw new HTTPError("Your IP is blocked from registration"); } + } - console.log("register", req.body.email, req.body.username, ip); - // TODO: automatically join invite - // TODO: gift_code_sku_id? - // TODO: check password strength + console.log("register", req.body.email, req.body.username, ip); + // TODO: automatically join invite + // TODO: gift_code_sku_id? + // TODO: check password strength - // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email = adjustEmail(email); + // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick + let adjusted_email = adjustEmail(email); - // adjusted_password will be the hash of the password - let adjusted_password = ""; + // adjusted_password will be the hash of the password + let adjusted_password = ""; - // trim special uf8 control characters -> Backspace, Newline, ... - let adjusted_username = trimSpecial(username); + // trim special uf8 control characters -> Backspace, Newline, ... + let adjusted_username = trimSpecial(username); - // discriminator will be randomly generated - let discriminator = ""; + // discriminator will be randomly generated + let discriminator = ""; - // check if registration is allowed - if (!register.allowNewRegistration) { - throw FieldErrors({ - email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") } - }); - } + // check if registration is allowed + if (!register.allowNewRegistration) { + throw FieldErrors({ + email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") } + }); + } - // check if the user agreed to the Terms of Service - if (!consent) { - throw FieldErrors({ - consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") } - }); - } + // check if the user agreed to the Terms of Service + if (!consent) { + throw FieldErrors({ + consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") } + }); + } - // require invite to register -> e.g. for organizations to send invites to their employees - if (register.requireInvite && !invite) { - throw FieldErrors({ - email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } - }); - } + // require invite to register -> e.g. for organizations to send invites to their employees + if (register.requireInvite && !invite) { + throw FieldErrors({ + email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } + }); + } - if (email) { - // replace all dots and chars after +, if its a gmail.com email - if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); + if (email) { + // replace all dots and chars after +, if its a gmail.com email + if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); - // check if there is already an account with this email - const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); + // check if there is already an account with this email + const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") - } - }); - } - } else if (register.email.necessary) { + if (exists) { throw FieldErrors({ - email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") + } }); } + } else if (register.email.necessary) { + throw FieldErrors({ + email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); + } + + if (register.dateOfBirth.necessary && !date_of_birth) { + throw FieldErrors({ + date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); + } else if (register.dateOfBirth.minimum) { + const minimum = new Date(); + minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); - if (register.dateOfBirth.necessary && !date_of_birth) { + // higher is younger + if (date_of_birth > minimum) { throw FieldErrors({ - date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + date_of_birth: { + code: "DATE_OF_BIRTH_UNDERAGE", + message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum }) + } }); - } else if (register.dateOfBirth.minimum) { - const minimum = new Date(); - minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); - - // higher is younger - if (date_of_birth > minimum) { - throw FieldErrors({ - date_of_birth: { - code: "DATE_OF_BIRTH_UNDERAGE", - message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum }) - } - }); - } - } - - if (!register.allowMultipleAccounts) { - // TODO: check if fingerprint was eligible generated - const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); - - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") - } - }); - } - } - - if (register.requireCaptcha && security.captcha.enabled) { - if (!captcha_key) { - const { sitekey, service } = security.captcha; - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service - }); - } - - // TODO: check captcha } + } - // the salt is saved in the password refer to bcrypt docs - adjusted_password = await bcrypt.hash(password, 12); - - let exists; - // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists - // if it all five times already exists, abort with USERNAME_TOO_MANY_USERS error - // else just continue - // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? - for (let tries = 0; tries < 5; tries++) { - discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); - if (!exists) break; - } + if (!register.allowMultipleAccounts) { + // TODO: check if fingerprint was eligible generated + const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); if (exists) { throw FieldErrors({ - username: { - code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS") + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") } }); } + } + + if (register.requireCaptcha && security.captcha.enabled) { + if (!captcha_key) { + const { sitekey, service } = security.captcha; + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service + }); + } + + // TODO: check captcha + } - // TODO: save date_of_birth - // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed - // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - - const user = await new User({ - created_at: new Date(), - username: adjusted_username, - discriminator, - id: Snowflake.generate(), - bot: false, - system: false, - desktop: false, - mobile: false, - premium: true, - premium_type: 2, - bio: "", - mfa_enabled: false, - verified: false, - disabled: false, - deleted: false, - email: adjusted_email, - nsfw_allowed: true, // TODO: depending on age - public_flags: "0", - flags: "0", // TODO: generate - data: { - hash: adjusted_password, - valid_tokens_since: new Date() - }, - settings: { ...defaultSettings, locale: req.language || "en-US" }, - fingerprints: [] - }).save(); - - return res.json({ token: await generateToken(user.id) }); + // the salt is saved in the password refer to bcrypt docs + adjusted_password = await bcrypt.hash(password, 12); + + let exists; + // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists + // if it all five times already exists, abort with USERNAME_TOO_MANY_USERS error + // else just continue + // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? + for (let tries = 0; tries < 5; tries++) { + discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); + exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); + if (!exists) break; } -); + + if (exists) { + throw FieldErrors({ + username: { + code: "USERNAME_TOO_MANY_USERS", + message: req.t("auth:register.USERNAME_TOO_MANY_USERS") + } + }); + } + + // TODO: save date_of_birth + // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed + // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false + + const user = await new User({ + created_at: new Date(), + username: adjusted_username, + discriminator, + id: Snowflake.generate(), + bot: false, + system: false, + desktop: false, + mobile: false, + premium: true, + premium_type: 2, + bio: "", + mfa_enabled: false, + verified: false, + disabled: false, + deleted: false, + email: adjusted_email, + nsfw_allowed: true, // TODO: depending on age + public_flags: "0", + flags: "0", // TODO: generate + data: { + hash: adjusted_password, + valid_tokens_since: new Date() + }, + settings: { ...defaultSettings, locale: req.language || "en-US" }, + fingerprints: [] + }).save(); + + return res.json({ token: await generateToken(user.id) }); +}); export function adjustEmail(email: string): string | undefined { // body parser already checked if it is a valid email diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 46554d70..02ac9884 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,48 +1,59 @@ -import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission } from "@fosscord/util"; +import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util"; import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; -import { ChannelModifySchema } from "../../../schema/Channel"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel // TODO: Get channel -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - return res.send(channel); }); -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); - permission.hasThrow("MANAGE_CHANNELS"); - // TODO: Dm channel "close" not delete const data = channel; - await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent); - - await Channel.delete({ id: channel_id }); + await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]); res.send(data); }); -router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response) => { +export interface ChannelModifySchema { + /** + * @maxLength 100 + */ + name: string; + type: ChannelType; + topic?: string; + bitrate?: number; + user_limit?: number; + rate_limit_per_user?: number; + position?: number; + permission_overwrites?: { + id: string; + type: ChannelPermissionOverwriteType; + allow: bigint; + deny: bigint; + }[]; + parent_id?: string; + id?: string; // is not used (only for guild create) + nsfw?: boolean; + rtc_region?: string; + default_auto_archive_duration?: number; +} + +router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { var payload = req.body as ChannelModifySchema; const { channel_id } = req.params; - const permission = await getPermission(req.user_id, undefined, channel_id); - permission.hasThrow("MANAGE_CHANNELS"); - const channel = await Channel.findOneOrFail({ id: channel_id }); channel.assign(payload); diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index c6909fd0..39263185 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -1,14 +1,25 @@ import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { random } from "@fosscord/api"; -import { InviteCreateSchema } from "../../../schema/Invite"; import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; import { isTextChannel } from "./messages"; const router: Router = Router(); -router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) => { +export interface InviteCreateSchema { + target_user_id?: string; + target_type?: string; + validate?: string; //? wtf is this + max_age?: number; + max_uses?: number; + temporary?: boolean; + unique?: boolean; + target_user?: string; + target_user_type?: number; +} + +router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE" }), async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] }); @@ -19,23 +30,6 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) } const { guild_id } = channel; - const permission = await getPermission(user_id, guild_id, undefined, { - guild_select: [ - "banner", - "description", - "features", - "icon", - "id", - "name", - "nsfw", - "nsfw_level", - "splash", - "vanity_url_code", - "verification_level" - ] as (keyof Guild)[] - }); - permission.hasThrow("CREATE_INSTANT_INVITE"); - const expires_at = new Date(req.body.max_age * 1000 + Date.now()); const invite = await new Invite({ @@ -52,14 +46,14 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) }).save(); const data = invite.toJSON(); data.inviter = await User.getPublicUser(req.user_id); - data.guild = permission.cache.guild; + data.guild = await Guild.findOne({ id: guild_id }); data.channel = channel; await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent); res.status(201).send(data); }); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); @@ -68,8 +62,6 @@ router.get("/", async (req: Request, res: Response) => { throw new HTTPError("This channel doesn't exist", 404); } const { guild_id } = channel; - const permission = await getPermission(user_id, guild_id); - permission.hasThrow("MANAGE_CHANNELS"); const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index aab51484..97d1d19e 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -1,14 +1,18 @@ import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; import { Request, Response, Router } from "express"; - -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); // TODO: check if message exists // TODO: send read state event to all channel members -router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req: Request, res: Response) => { +export interface MessageAcknowledgeSchema { + manual?: boolean; + mention_count?: number; +} + +router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const permission = await getPermission(req.user_id, undefined, channel_id); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index e9588bd3..d0f780db 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,12 +1,13 @@ import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Response, Request } from "express"; -import { MessageCreateSchema } from "../../../../../schema/Message"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { handleMessage, postHandleMessage } from "@fosscord/api"; +import { MessageCreateSchema } from "../index"; const router = Router(); +// TODO: message content/embed string length limit -router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; @@ -47,14 +48,17 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response // TODO: delete attachments in message -router.delete("/", async (req: Request, res: Response) => { +// permission check only if deletes messagr from other user +router.delete("/", route({}), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - if (message.author_id !== req.user_id) permission.hasThrow("MANAGE_MESSAGES"); + if (message.author_id !== req.user_id) { + const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + permission.hasThrow("MANAGE_MESSAGES"); + } await Message.delete({ id: message_id }); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts index f60484b5..f2b83d40 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -13,6 +13,7 @@ import { PublicUserProjection, User } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { In } from "typeorm"; @@ -35,14 +36,11 @@ function getEmoji(emoji: string): PartialEmoji { }; } -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - await Message.update({ id: message_id, channel_id }, { reactions: [] }); await emitEvent({ @@ -58,13 +56,10 @@ router.delete("/", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/:emoji", async (req: Request, res: Response) => { +router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); @@ -88,7 +83,7 @@ router.delete("/:emoji", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.get("/:emoji", async (req: Request, res: Response) => { +router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); @@ -96,9 +91,6 @@ router.get("/:emoji", async (req: Request, res: Response) => { const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!reaction) throw new HTTPError("Reaction not found", 404); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("VIEW_CHANNEL"); - const users = await User.find({ where: { id: In(reaction.user_ids) @@ -109,7 +101,7 @@ router.get("/:emoji", async (req: Request, res: Response) => { res.json(users); }); -router.put("/:emoji/:user_id", async (req: Request, res: Response) => { +router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY" }), async (req: Request, res: Response) => { const { message_id, channel_id, user_id } = req.params; if (user_id !== "@me") throw new HTTPError("Invalid user"); const emoji = getEmoji(req.params.emoji); @@ -118,13 +110,11 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("READ_MESSAGE_HISTORY"); - if (!already_added) permissions.hasThrow("ADD_REACTIONS"); + if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); if (emoji.id) { const external_emoji = await Emoji.findOneOrFail({ id: emoji.id }); - if (!already_added) permissions.hasThrow("USE_EXTERNAL_EMOJIS"); + if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); emoji.animated = external_emoji.animated; emoji.name = external_emoji.name; } @@ -154,7 +144,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { +router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => { var { message_id, channel_id, user_id } = req.params; const emoji = getEmoji(req.params.emoji); @@ -162,10 +152,11 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id, channel_id }); - const permissions = await getPermission(req.user_id, undefined, channel_id); - if (user_id === "@me") user_id = req.user_id; - else permissions.hasThrow("MANAGE_MESSAGES"); + else { + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + } const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 5d7566e1..a0fe7cc0 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -1,18 +1,21 @@ import { Router, Response, Request } from "express"; import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); export default router; +export interface BulkDeleteSchema { + messages: string[]; +} + // TODO: should users be able to bulk delete messages or only bots? // TODO: should this request fail, if you provide messages older than 14 days/invalid ids? // https://discord.com/developers/docs/resources/channel#bulk-delete-messages -router.post("/", check({ messages: [String] }), async (req: Request, res: Response) => { +router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 591ebbbe..11334367 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,10 +1,8 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { MessageCreateSchema } from "../../../../schema/Message"; -import { check, instanceOf, Length } from "@fosscord/api"; +import { instanceOf, Length, route } from "@fosscord/api"; import multer from "multer"; -import { Query } from "mongoose"; import { sendMessage } from "@fosscord/api"; import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; @@ -31,6 +29,30 @@ export function isTextChannel(type: ChannelType): boolean { } } +export interface MessageCreateSchema { + content?: string; + nonce?: string; + tts?: boolean; + flags?: string; + embeds?: Embed[]; + embed?: Embed; + // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) + allowed_mentions?: { + parse?: string[]; + roles?: string[]; + users?: string[]; + replied_user?: boolean; + }; + message_reference?: { + message_id: string; + channel_id: string; + guild_id?: string; + fail_if_not_exists?: boolean; + }; + payload_json?: string; + file?: any; +} + // https://discord.com/developers/docs/resources/channel#create-message // get messages router.get("/", async (req: Request, res: Response) => { @@ -109,39 +131,44 @@ const messageUpload = multer({ // TODO: check allowed_mentions // Send message -router.post("/", messageUpload.single("file"), async (req: Request, res: Response) => { - const { channel_id } = req.params; - var body = req.body as MessageCreateSchema; - const attachments: Attachment[] = []; - - if (req.file) { - try { - const file = await uploadFile(`/attachments/${channel_id}`, req.file); - attachments.push({ ...file, proxy_url: file.url }); - } catch (error) { - return res.status(400).json(error); +router.post( + "/", + messageUpload.single("file"), + async (req, res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); } - } - if (body.payload_json) { - body = JSON.parse(body.payload_json); - } + next(); + }, + route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + var body = req.body as MessageCreateSchema; + const attachments: Attachment[] = []; + + if (req.file) { + try { + const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); + attachments.push({ ...file, proxy_url: file.url }); + } catch (error) { + return res.status(400).json(error); + } + } - const errors = instanceOf(MessageCreateSchema, body, { req }); - if (errors !== true) throw errors; - - const embeds = []; - if (body.embed) embeds.push(body.embed); - const data = await sendMessage({ - ...body, - type: 0, - pinned: false, - author_id: req.user_id, - embeds, - channel_id, - attachments, - edited_timestamp: undefined - }); - - return res.json(data); -}); + const embeds = []; + if (body.embed) embeds.push(body.embed); + const data = await sendMessage({ + ...body, + type: 0, + pinned: false, + author_id: req.user_id, + embeds, + channel_id, + attachments, + edited_timestamp: undefined + }); + + return res.json(data); + } +); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 0465ca31..827e46f2 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -2,61 +2,61 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) -router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, id: String }), async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; - const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; +export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite {} - var channel = await Channel.findOneOrFail({ id: channel_id }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); +router.put( + "/:overwrite_id", + route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { channel_id, overwrite_id } = req.params; + const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("MANAGE_ROLES"); + var channel = await Channel.findOneOrFail({ id: channel_id }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - if (body.type === 0) { - if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); - } else if (body.type === 1) { - if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); - } else throw new HTTPError("type not supported", 501); + if (body.type === 0) { + if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + } else if (body.type === 1) { + if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); + } else throw new HTTPError("type not supported", 501); - // @ts-ignore - var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); - if (!overwrite) { // @ts-ignore - overwrite = { - id: overwrite_id, - type: body.type, - allow: body.allow, - deny: body.deny - }; - channel.permission_overwrites.push(overwrite); + var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); + if (!overwrite) { + // @ts-ignore + overwrite = { + id: overwrite_id, + type: body.type, + allow: body.allow, + deny: body.deny + }; + channel.permission_overwrites.push(overwrite); + } + overwrite.allow = body.allow; + overwrite.deny = body.deny; + + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel + } as ChannelUpdateEvent) + ]); + + return res.sendStatus(204); } - overwrite.allow = body.allow; - overwrite.deny = body.deny; - - // @ts-ignore - channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, channel, { new: true }); - - await emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel - } as ChannelUpdateEvent); - - return res.sendStatus(204); -}); +); // TODO: check permission hierarchy -router.delete("/:overwrite_id", async (req: Request, res: Response) => { +router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { channel_id, overwrite_id } = req.params; - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_ROLES"); - const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index 33309c86..e71e659f 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -1,19 +1,26 @@ -import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent } from "@fosscord/util"; +import { + Channel, + ChannelPinsUpdateEvent, + Config, + emitEvent, + getPermission, + Message, + MessageUpdateEvent, + DiscordApiErrors +} from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { DiscordApiErrors } from "@fosscord/util"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.put("/:message_id", async (req: Request, res: Response) => { +router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const message = await Message.findOneOrFail({ id: message_id }); - const permission = await getPermission(req.user_id, message.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true }); const { maxPins } = Config.get().limits.channel; @@ -26,7 +33,6 @@ router.put("/:message_id", async (req: Request, res: Response) => { channel_id, data: message } as MessageUpdateEvent), - emitEvent({ event: "CHANNEL_PINS_UPDATE", channel_id, @@ -41,14 +47,11 @@ router.put("/:message_id", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/:message_id", async (req: Request, res: Response) => { +router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); + if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); const message = await Message.findOneOrFail({ id: message_id }); message.pinned = false; @@ -76,13 +79,9 @@ router.delete("/:message_id", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - let pins = await Message.find({ channel_id: channel_id, pinned: true }); res.send(pins); diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index f1fb3c86..ad973bca 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -1,11 +1,10 @@ import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; - const router: Router = Router(); -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const user_id = req.user_id; const timestamp = Date.now(); @@ -24,6 +23,7 @@ router.post("/", async (req: Request, res: Response) => { guild_id: channel.guild_id } } as TypingStartEvent); + res.sendStatus(204); }); diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 821a62db..f84dfcc5 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { check, Length } from "@fosscord/api"; +import { check, Length, route } from "@fosscord/api"; import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; @@ -7,9 +7,16 @@ import { DiscordApiErrors } from "@fosscord/util"; const router: Router = Router(); // TODO: webhooks +export interface WebhookCreateSchema { + /** + * @maxLength 80 + */ + name: string; + avatar: string; +} // TODO: use Image Data Type for avatar instead of String -router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req: Request, res: Response) => { +router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { const channel_id = req.params.channel_id; const channel = await Channel.findOneOrFail({ id: channel_id }); @@ -20,12 +27,11 @@ router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), as const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - const permission = await getPermission(req.user_id, channel.guild_id); - permission.hasThrow("MANAGE_WEBHOOKS"); - var { avatar, name } = req.body as { name: string; avatar?: string }; name = trimSpecial(name); if (name === "clyde") throw new HTTPError("Invalid name", 400); + + // TODO: save webhook in database and send response }); export default router; diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts index 0808727f..f667eb2a 100644 --- a/api/src/routes/discoverable-guilds.ts +++ b/api/src/routes/discoverable-guilds.ts @@ -1,10 +1,10 @@ import { Guild } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { In } from "typeorm"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { limit } = req.params; // ! this only works using SQL querys diff --git a/api/src/routes/experiments.ts b/api/src/routes/experiments.ts index 3bdbed62..966ed99c 100644 --- a/api/src/routes/experiments.ts +++ b/api/src/routes/experiments.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.send({ fingerprint: "", assignments: [] }); }); diff --git a/api/src/routes/gateway.ts b/api/src/routes/gateway.ts index 3120718e..d4208341 100644 --- a/api/src/routes/gateway.ts +++ b/api/src/routes/gateway.ts @@ -1,9 +1,10 @@ import { Config } from "@fosscord/util"; import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { const { endpoint } = Config.get().gateway; res.json({ url: endpoint || process.env.GATEWAY || "ws://localhost:3002" }); }); diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 41a97b6a..c7fda9ad 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -2,7 +2,11 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress, check, route } from "@fosscord/api"; -import { BanCreateSchema } from "@fosscord/api/schema/Ban"; + +export interface BanCreateSchema { + delete_message_days?: string; + reason?: string; +} const router: Router = Router(); router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { @@ -27,7 +31,7 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER const banned_user = await User.getPublicUser(banned_user_id); if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); - if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); const ban = new Ban({ user_id: banned_user_id, diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index faeecb76..e21327d1 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,9 +1,8 @@ import { Router, Response, Request } from "express"; import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { ChannelModifySchema } from "../../../schema/Channel"; - -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; +import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); router.get("/", async (req: Request, res: Response) => { @@ -13,10 +12,7 @@ router.get("/", async (req: Request, res: Response) => { res.json(channels); }); -// TODO: check if channel type is permitted -// TODO: check if parent_id exists - -router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel const { guild_id } = req.params; const body = req.body as ChannelModifySchema; @@ -26,45 +22,39 @@ router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) res.status(201).json(channel); }); -// TODO: check if parent_id exists -router.patch( - "/", - check([{ id: String, $position: Number, $lock_permissions: Boolean, $parent_id: String }]), - async (req: Request, res: Response) => { - // changes guild channel position - const { guild_id } = req.params; - const body = req.body as { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; - - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_CHANNELS"); - - await Promise.all([ - body.map(async (x) => { - if (!x.position && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); +export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; - const opts: any = {}; - if (x.position) opts.position = x.position; - - if (x.parent_id) { - opts.parent_id = x.parent_id; - const parent_channel = await Channel.findOneOrFail({ - where: { id: x.parent_id, guild_id }, - select: ["permission_overwrites"] - }); - if (x.lock_permissions) { - opts.permission_overwrites = parent_channel.permission_overwrites; - } +router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { + // changes guild channel position + const { guild_id } = req.params; + const body = req.body as ChannelReorderSchema; + + await Promise.all([ + body.map(async (x) => { + if (!x.position && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); + + const opts: any = {}; + if (x.position) opts.position = x.position; + + if (x.parent_id) { + opts.parent_id = x.parent_id; + const parent_channel = await Channel.findOneOrFail({ + where: { id: x.parent_id, guild_id }, + select: ["permission_overwrites"] + }); + if (x.lock_permissions) { + opts.permission_overwrites = parent_channel.permission_overwrites; } + } - await Channel.update({ guild_id, id: x.id }, opts); - const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); + await Channel.update({ guild_id, id: x.id }, opts); + const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); - await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); - }) - ]); + await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); + }) + ]); - res.sendStatus(204); - } -); + res.sendStatus(204); +}); export default router; diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index bbbd1fa4..7c3c5530 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -1,12 +1,13 @@ import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; +import { route } from "@fosscord/api"; const router = Router(); // discord prefixes this route with /delete instead of using the delete method // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({}), async (req: Request, res: Response) => { var { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 244900ec..690d4103 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,23 +1,36 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { GuildUpdateSchema } from "../../../schema/Guild"; - -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; +import { GuildCreateSchema } from "../index"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface GuildUpdateSchema extends Omit { + banner?: string; + splash?: string; + description?: string; + features?: string[]; + verification_level?: number; + default_message_notifications?: number; + system_channel_flags?: number; + explicit_content_filter?: number; + public_updates_channel_id?: string; + afk_timeout?: number; + afk_channel_id?: string; + preferred_locale?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const [guild, member_count, member] = await Promise.all([ + const [guild, member] = await Promise.all([ Guild.findOneOrFail({ id: guild_id }), - Member.count({ guild_id: guild_id, id: req.user_id }), - Member.findOneOrFail({ id: req.user_id }) + Member.findOne({ guild_id: guild_id, id: req.user_id }) ]); - if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); // @ts-ignore guild.joined_at = member?.joined_at; @@ -25,14 +38,11 @@ router.get("/", async (req: Request, res: Response) => { return res.json(guild); }); -router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const body = req.body as GuildUpdateSchema; const { guild_id } = req.params; // TODO: guild update check image - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts index 39a934ee..b7534e31 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/api/src/routes/guilds/#guild_id/invites.ts @@ -1,14 +1,12 @@ import { getPermission, Invite, PublicInviteRelation } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const permissions = await getPermission(req.user_id, guild_id); - permissions.hasThrow("MANAGE_GUILD"); - const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); return res.json(invites); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 8b04a508..1708b7eb 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,23 +1,15 @@ import { Request, Response, Router } from "express"; -import { - Guild, - Member, - User, - GuildMemberAddEvent, - getPermission, - PermissionResolvable, - Role, - GuildMemberUpdateEvent, - emitEvent -} from "@fosscord/util"; +import { Member, getPermission, Role, GuildMemberUpdateEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { MemberChangeSchema } from "@fosscord/api/schema/Member"; -import { In } from "typeorm"; +import { check, route } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface MemberChangeSchema { + roles?: string[]; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); @@ -26,8 +18,9 @@ router.get("/", async (req: Request, res: Response) => { return res.json(member); }); -router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; +router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => { + let { guild_id, member_id } = req.params; + if (member_id === "@me") member_id = req.user_id; const body = req.body as MemberChangeSchema; const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); @@ -39,7 +32,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) } await member.save(); - // do not use promise.all as we have to first write to db before emitting the event + // do not use promise.all as we have to first write to db before emitting the event to catch errors await emitEvent({ event: "GUILD_MEMBER_UPDATE", guild_id, @@ -49,7 +42,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) res.json(member); }); -router.put("/", async (req: Request, res: Response) => { +router.put("/", route({}), async (req: Request, res: Response) => { let { guild_id, member_id } = req.params; if (member_id === "@me") member_id = req.user_id; @@ -59,12 +52,9 @@ router.put("/", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("KICK_MEMBERS"); - await Member.removeFromGuild(member_id, guild_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts index 3f2975e6..27f7f65d 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -1,11 +1,14 @@ import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; -import { check } from "lambert-server"; -import { MemberNickChangeSchema } from "../../../../../schema/Member"; const router = Router(); -router.patch("/", check(MemberNickChangeSchema), async (req: Request, res: Response) => { +export interface MemberNickChangeSchema { + nick: string; +} + +router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => { var { guild_id, member_id } = req.params; var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; if (member_id === "@me") { diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index cb9bad9a..ae10be82 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -1,24 +1,19 @@ import { getPermission, Member } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); -router.delete("/:member_id/roles/:role_id", async (req: Request, res: Response) => { +router.delete("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); -router.put("/:member_id/roles/:role_id", async (req: Request, res: Response) => { +router.put("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - await Member.addRole(member_id, guild_id, role_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 198d6946..335f21c7 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,13 +1,15 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length } from "@fosscord/api"; +import { instanceOf, Length, route } from "@fosscord/api"; import { MoreThan } from "typeorm"; const router = Router(); // TODO: not allowed for user -> only allowed for bots with privileged intents // TODO: send over websocket -router.get("/", async (req: Request, res: Response) => { +// TODO: check for GUILD_MEMBERS intent + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); await Member.IsInGuildOrFail(req.user_id, guild_id); diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts index 86208b79..75d24fd1 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/api/src/routes/guilds/#guild_id/regions.ts @@ -1,11 +1,11 @@ import { Config, Guild, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { getVoiceRegions } from "@fosscord/api"; +import { getVoiceRegions, route } from "@fosscord/api"; import { getIpAdress } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); //TODO we should use an enum for guild's features and not hardcoded strings diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 76dd47c5..6b2902d9 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -2,23 +2,34 @@ import { Request, Response, Router } from "express"; import { Role, getPermission, - Snowflake, Member, GuildRoleCreateEvent, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, - Config + Config, + DiscordApiErrors } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; -import { RoleModifySchema, RolePositionUpdateSchema } from "../../../schema/Roles"; -import { DiscordApiErrors } from "@fosscord/util"; +import { check, route } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); +export interface RoleModifySchema { + name?: string; + permissions?: bigint; + color?: number; + hoist?: boolean; // whether the role should be displayed separately in the sidebar + mentionable?: boolean; // whether the role should be mentionable + position?: number; +} + +export type RolePositionUpdateSchema = { + id: string; + position: number; +}[]; + router.get("/", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; @@ -29,13 +40,10 @@ router.get("/", async (req: Request, res: Response) => { return res.json(roles); }); -router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as RoleModifySchema; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - const role_count = await Role.count({ guild_id }); const { maxRoles } = Config.get().limits.guild; @@ -50,7 +58,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => ...body, guild_id: guild_id, managed: false, - permissions: String(perms.bitfield & (body.permissions || 0n)), + permissions: String(req.permission!.bitfield & (body.permissions || 0n)), tags: undefined }); @@ -69,14 +77,11 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => res.json(role); }); -router.delete("/:role_id", async (req: Request, res: Response) => { +router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const { role_id } = req.params; if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); - const permissions = await getPermission(req.user_id, guild_id); - permissions.hasThrow("MANAGE_ROLES"); - await Promise.all([ Role.delete({ id: role_id, @@ -97,14 +102,11 @@ router.delete("/:role_id", async (req: Request, res: Response) => { // TODO: check role hierarchy -router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Response) => { +router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { role_id, guild_id } = req.params; const body = req.body as RoleModifySchema; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - - const role = new Role({ ...body, id: role_id, guild_id, permissions: String(perms.bitfield & (body.permissions || 0n)) }); + const role = new Role({ ...body, id: role_id, guild_id, permissions: String(req.permission!.bitfield & (body.permissions || 0n)) }); await Promise.all([ role.save(), @@ -121,7 +123,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res res.json(role); }); -router.patch("/", check(RolePositionUpdateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const body = req.body as RolePositionUpdateSchema; diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index e9304e11..5179e761 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { Guild, getPermission, Template } from "@fosscord/util"; +import { Guild, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { generateCode } from "@fosscord/api"; const router: Router = Router(); @@ -24,7 +23,17 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "icon" ]; -router.get("/", async (req: Request, res: Response) => { +export interface TemplateCreateSchema { + name: string; + description?: string; +} + +export interface TemplateModifySchema { + name: string; + description?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; var templates = await Template.find({ source_guild_id: guild_id }); @@ -32,12 +41,9 @@ router.get("/", async (req: Request, res: Response) => { return res.json(templates); }); -router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); @@ -54,44 +60,31 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response res.json(template); }); -router.delete("/:code", async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const { code } = req.params; - - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); +router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + const { code, guild_id } = req.params; const template = await Template.delete({ - code + code, + source_guild_id: guild_id }); res.json(template); }); -router.put("/:code", async (req: Request, res: Response) => { - // synchronizes the template +router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const template = await new Template({ code, serialized_source_guild: guild }).save(); res.json(template); }); -router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => { - // updates the template description - const { guild_id } = req.params; - const { code } = req.params; +router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + const { code, guild_id } = req.params; const { name, description } = req.body; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - - const template = await new Template({ code, name: name, description: description }).save(); + const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save(); res.json(template); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index f1887cc0..9c0989cc 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,35 +1,37 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; -import { check, Length } from "@fosscord/api"; +import { check, Length, route } from "@fosscord/api"; const router = Router(); const InviteRegex = /\W/g; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["vanity_url"] }); if (!guild.vanity_url) return res.json({ code: null }); return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses }); }); +export interface VanityUrlSchema { + /** + * @minLength 1 + * @maxLength 20 + */ + code?: string; +} + // TODO: check if guild is elgible for vanity url -router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Request, res: Response) => { +router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const code = req.body.code.replace(InviteRegex); + const body = req.body as VanityUrlSchema; + const code = body.code?.replace(InviteRegex, ""); await Invite.findOneOrFail({ code }); const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_GUILD"); - const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); guild.vanity_url_code = code; diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 447e15c1..3d76938b 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,14 +1,59 @@ -import { check } from "@fosscord/api"; -import { VoiceStateUpdateSchema } from "../../../../../schema"; +import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; +import { check, route } from "@fosscord/api"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "@fosscord/api"; const router = Router(); +//TODO need more testing when community guild and voice stage channel are working -router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { +export interface VoiceStateUpdateSchema { + channel_id: string; + guild_id?: string; + suppress?: boolean; + request_to_speak_timestamp?: Date; + self_mute?: boolean; + self_deaf?: boolean; + self_video?: boolean; +} + +router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; - const { guild_id, user_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id, user_id); + var { guild_id, user_id } = req.params; + if (user_id === "@me") user_id = req.user_id; + + const perms = await getPermission(req.user_id, guild_id, body.channel_id); + + /* + From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state + You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself. + You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. + */ + if (body.suppress && user_id !== req.user_id) { + perms.hasThrow("MUTE_MEMBERS"); + } + if (!body.suppress) body.request_to_speak_timestamp = new Date(); + if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); + + const voice_state = await VoiceState.findOne({ + guild_id, + channel_id: body.channel_id, + user_id + }); + if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; + + voice_state.assign(body); + const channel = await Channel.findOneOrFail({ guild_id, id: body.channel_id }); + if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { + throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; + } + + await Promise.all([ + voice_state.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voice_state, + guild_id + } as VoiceStateUpdateEvent) + ]); return res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts deleted file mode 100644 index b637ff66..00000000 --- a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { check } from "@fosscord/api"; -import { VoiceStateUpdateSchema } from "../../../../../schema"; -import { Request, Response, Router } from "express"; -import { updateVoiceState } from "@fosscord/api"; - -const router = Router(); - -router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { - const body = req.body as VoiceStateUpdateSchema; - const { guild_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id); - return res.sendStatus(204); -}); - -export default router; diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index 7ca49b4e..7141f17e 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -1,31 +1,36 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; -import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface GuildUpdateWelcomeScreenSchema { + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name: string; + }[]; + enabled?: boolean; + description?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const guild = await Guild.findOneOrFail({ id: guild_id }); - await Member.IsInGuildOrFail(req.user_id, guild_id); res.json(guild.welcome_screen); }); -router.patch("/", check(GuildUpdateWelcomeScreenSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as GuildUpdateWelcomeScreenSchema; const guild = await Guild.findOneOrFail({ id: guild_id }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400); if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid if (body.description) guild.welcome_screen.description = body.description; diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index f871fac8..c31519fa 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { random } from "@fosscord/api"; +import { random, route } from "@fosscord/api"; const router: Router = Router(); @@ -14,7 +14,7 @@ const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget // TODO: Cache the response for a guild for 5 minutes regardless of response -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/api/src/routes/guilds/#guild_id/widget.png.ts index 89b31153..4c82b740 100644 --- a/api/src/routes/guilds/#guild_id/widget.png.ts +++ b/api/src/routes/guilds/#guild_id/widget.png.ts @@ -1,6 +1,7 @@ import { Request, Response, Router } from "express"; import { Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; +import { route } from "@fosscord/api"; import fs from "fs"; import path from "path"; @@ -10,7 +11,7 @@ const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-image // TODO: Cache the response -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index d9ce817e..c8caae14 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,31 +1,29 @@ import { Request, Response, Router } from "express"; import { getPermission, Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { WidgetModifySchema } from "../../../schema/Widget"; +import { check, route } from "@fosscord/api"; + +export interface WidgetModifySchema { + enabled: boolean; // whether the widget is enabled + channel_id: string; // the widget channel id +} const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-settings -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ id: guild_id }); return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null }); }); // https://discord.com/developers/docs/resources/guild#modify-guild-widget -router.patch("/", check(WidgetModifySchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const body = req.body as WidgetModifySchema; const { guild_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id }); // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index a1b199e7..ba951f96 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,15 +1,28 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { GuildCreateSchema } from "../../schema/Guild"; +import { check, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; +import { ChannelModifySchema } from "../channels/#channel_id"; const router: Router = Router(); +export interface GuildCreateSchema { + /** + * @maxLength 100 + */ + name: string; + region?: string; + icon?: string; + channels?: ChannelModifySchema[]; + guild_template_code?: string; + system_channel_id?: string; + rules_channel_id?: string; +} + //TODO: create default channel -router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index 1d0f2716..d7a42044 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -2,11 +2,15 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; -router.get("/:code", async (req: Request, res: Response) => { +export interface GuildTemplateCreateSchema { + name: string; + avatar?: string; +} + +router.get("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const template = await Template.findOneOrFail({ code: code }); @@ -14,7 +18,7 @@ router.get("/:code", async (req: Request, res: Response) => { res.json(template); }); -router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => { +router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => { const { code } = req.params; const body = req.body as GuildTemplateCreateSchema; diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index b8c24c1f..6e77a853 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,9 +1,10 @@ import { Router, Request, Response } from "express"; import { getPermission, Guild, Invite, Member, PublicInviteRelation } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; const router: Router = Router(); -router.get("/:code", async (req: Request, res: Response) => { +router.get("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ where: { code }, relations: PublicInviteRelation }); @@ -11,7 +12,7 @@ router.get("/:code", async (req: Request, res: Response) => { res.status(200).send(invite); }); -router.post("/:code", async (req: Request, res: Response) => { +router.post("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ code }); @@ -23,7 +24,8 @@ router.post("/:code", async (req: Request, res: Response) => { res.status(200).send(invite); }); -router.delete("/:code", async (req: Request, res: Response) => { +// * cant use permission of route() function because path doesn't have guild_id/channel_id +router.delete("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ code }); const { guild_id, channel_id } = invite; diff --git a/api/src/routes/ping.ts b/api/src/routes/ping.ts index 38daf81e..5cdea705 100644 --- a/api/src/routes/ping.ts +++ b/api/src/routes/ping.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { res.send("pong"); }); diff --git a/api/src/routes/science.ts b/api/src/routes/science.ts index b16ef783..8556a3ad 100644 --- a/api/src/routes/science.ts +++ b/api/src/routes/science.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.post("/", (req: Request, res: Response) => { +router.post("/", route({}), (req: Request, res: Response) => { // TODO: res.sendStatus(204); }); diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts index 07956360..bdb1060f 100644 --- a/api/src/routes/users/#id/index.ts +++ b/api/src/routes/users/#id/index.ts @@ -1,9 +1,10 @@ import { Router, Request, Response } from "express"; import { User } from "@fosscord/util"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { id } = req.params; res.json(await User.getPublicUser(id)); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 0f43a82f..d60c4f86 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -1,9 +1,17 @@ import { Router, Request, Response } from "express"; import { PublicConnectedAccount, PublicUser, User, UserPublic } from "@fosscord/util"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface UserProfileResponse { + user: UserPublic; + connected_accounts: PublicConnectedAccount; + premium_guild_since?: Date; + premium_since?: Date; +} + +router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req: Request, res: Response) => { if (req.params.id === "@me") req.params.id = req.user_id; const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); @@ -25,11 +33,4 @@ router.get("/", async (req: Request, res: Response) => { }); }); -export interface UserProfileResponse { - user: UserPublic; - connected_accounts: PublicConnectedAccount; - premium_guild_since?: Date; - premium_since?: Date; -} - export default router; diff --git a/api/src/routes/users/@me/affinities/guilds.ts b/api/src/routes/users/@me/affinities/guilds.ts index fa6be0e7..8d744744 100644 --- a/api/src/routes/users/@me/affinities/guilds.ts +++ b/api/src/routes/users/@me/affinities/guilds.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.status(200).send({ guild_affinities: [] }); }); diff --git a/api/src/routes/users/@me/affinities/user.ts b/api/src/routes/users/@me/affinities/user.ts index 0790a8a4..6d4e4991 100644 --- a/api/src/routes/users/@me/affinities/user.ts +++ b/api/src/routes/users/@me/affinities/user.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); }); diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 77fc8296..5515a217 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,19 +1,23 @@ import { Router, Request, Response } from "express"; import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { DmChannelCreateSchema } from "../../../schema/Channel"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { In } from "typeorm"; +export interface DmChannelCreateSchema { + name?: string; + recipients: string[]; +} + const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] }); res.json(recipients.map((x) => x.channel)); }); -router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); diff --git a/api/src/routes/users/@me/delete.ts b/api/src/routes/users/@me/delete.ts index e5fda948..39ceefd9 100644 --- a/api/src/routes/users/@me/delete.ts +++ b/api/src/routes/users/@me/delete.ts @@ -1,10 +1,12 @@ import { Router, Request, Response } from "express"; import { Guild, Member, User } from "@fosscord/util"; +import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; + const router = Router(); -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }); //User object let correctpass = true; diff --git a/api/src/routes/users/@me/devices.ts b/api/src/routes/users/@me/devices.ts index b16ef783..8556a3ad 100644 --- a/api/src/routes/users/@me/devices.ts +++ b/api/src/routes/users/@me/devices.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.post("/", (req: Request, res: Response) => { +router.post("/", route({}), (req: Request, res: Response) => { // TODO: res.sendStatus(204); }); diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index 7b8a130c..259ced96 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -1,10 +1,11 @@ import { User } from "@fosscord/util"; import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; const router = Router(); -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }); //User object let correctpass = true; diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts index fb88281b..4ba03cec 100644 --- a/api/src/routes/users/@me/guilds.ts +++ b/api/src/routes/users/@me/guilds.ts @@ -1,18 +1,18 @@ import { Router, Request, Response } from "express"; import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { In } from "typeorm"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } }); res.json(members.map((x) => x.guild)); }); // user send to leave a certain guild -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", route({}), async (req: Request, res: Response) => { const guild_id = req.params.id; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 451a657c..68723374 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,16 +1,33 @@ import { Router, Request, Response } from "express"; import { User, PrivateUserProjection } from "@fosscord/util"; -import { UserModifySchema } from "../../../schema/User"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; const router: Router = Router(); +export interface UserModifySchema { + /** + * @minLength 1 + * @maxLength 100 + */ + username?: string; + avatar?: string | null; + /** + * @maxLength 1024 + */ + bio?: string; + accent_color?: number | null; + banner?: string | null; + password?: string; + new_password?: string; + code?: string; +} + router.get("/", async (req: Request, res: Response) => { - res.json(await User.getPublicUser(req.user_id, { select: PrivateUserProjection })); + res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } })); }); -router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => { const body = req.body as UserModifySchema; if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); diff --git a/api/src/routes/users/@me/library.ts b/api/src/routes/users/@me/library.ts index d771cb5e..7ac13bae 100644 --- a/api/src/routes/users/@me/library.ts +++ b/api/src/routes/users/@me/library.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.status(200).send([]); }); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 67ca2f35..cc264f3f 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -11,19 +11,95 @@ import { import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { DiscordApiErrors } from "@fosscord/util"; - -import { check, Length } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] }); return res.json(user.relationships); }); +export interface RelationshipPutSchema { + type: RelationshipType; +} + +router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), + req.body.type + ); +}); + +export interface RelationshipPostSchema { + discriminator: string; + username: string; +} + +router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + relations: ["relationships"], + select: userProjection, + where: req.body as { discriminator: string; username: string } + }), + req.body.type + ); +}); + +router.delete("/:id", route({}), async (req: Request, res: Response) => { + const { id } = req.params; + if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); + + const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); + + const relationship = user.relationships.find((x) => x.id === id); + const friendRequest = friend.relationships.find((x) => x.id === req.user_id); + + if (relationship?.type === RelationshipType.blocked) { + // unblock user + user.relationships.remove(relationship); + + await Promise.all([ + user.save(), + emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent) + ]); + return res.sendStatus(204); + } + if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); + if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); + + user.relationships.remove(relationship); + friend.relationships.remove(friendRequest); + + await Promise.all([ + user.save(), + friend.save(), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: relationship, + user_id: req.user_id + } as RelationshipRemoveEvent), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest, + user_id: id + } as RelationshipRemoveEvent) + ]); + + return res.sendStatus(204); +}); + +export default router; + async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) { const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); @@ -114,71 +190,3 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ return res.sendStatus(204); } - -router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), - req.body.type - ); -}); - -router.post("/", check({ discriminator: String, username: String }), async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - relations: ["relationships"], - select: userProjection, - where: req.body as { discriminator: string; username: string } - }), - req.body.type - ); -}); - -router.delete("/:id", async (req: Request, res: Response) => { - const { id } = req.params; - if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); - - const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); - - const relationship = user.relationships.find((x) => x.id === id); - const friendRequest = friend.relationships.find((x) => x.id === req.user_id); - - if (relationship?.type === RelationshipType.blocked) { - // unblock user - user.relationships.remove(relationship); - - await Promise.all([ - user.save(), - emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent) - ]); - return res.sendStatus(204); - } - if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); - if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - - user.relationships.remove(relationship); - friend.relationships.remove(friendRequest); - - await Promise.all([ - user.save(), - friend.save(), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: relationship, - user_id: req.user_id - } as RelationshipRemoveEvent), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest, - user_id: id - } as RelationshipRemoveEvent) - ]); - - return res.sendStatus(204); -}); - -export default router; diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index e7db85ed..9d7a2545 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -1,11 +1,12 @@ import { Router, Response, Request } from "express"; import { User, UserSettings } from "@fosscord/util"; -import { check } from "@fosscord/api"; -import { UserSettingsSchema } from "../../../schema/User"; +import { route } from "@fosscord/api"; const router = Router(); -router.patch("/", check(UserSettingsSchema), async (req: Request, res: Response) => { +export interface UserSettingsSchema extends UserSettings {} + +router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => { const body = req.body as UserSettings; // only users can update user settings diff --git a/api/src/routes/voice/regions.ts b/api/src/routes/voice/regions.ts index da1aaade..4de304ee 100644 --- a/api/src/routes/voice/regions.ts +++ b/api/src/routes/voice/regions.ts @@ -1,10 +1,10 @@ import { Router, Request, Response } from "express"; -import { getIpAdress } from "@fosscord/api"; +import { getIpAdress, route } from "@fosscord/api"; import { getVoiceRegions } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true? }); diff --git a/api/src/schema/Ban.ts b/api/src/schema/Ban.ts deleted file mode 100644 index 947a60ea..00000000 --- a/api/src/schema/Ban.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const BanCreateSchema = { - $delete_message_days: String, - $reason: String, -}; - -export interface BanCreateSchema { - delete_message_days?: string; - reason?: string; -} diff --git a/api/src/schema/Channel.ts b/api/src/schema/Channel.ts deleted file mode 100644 index cfbc7205..00000000 --- a/api/src/schema/Channel.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ChannelType } from "@fosscord/util"; -import { Length } from "../util/instanceOf"; - -export const ChannelModifySchema = { - name: new Length(String, 2, 100), - type: new Length(Number, 0, 13), - $topic: new Length(String, 0, 1024), - $bitrate: Number, - $user_limit: Number, - $rate_limit_per_user: new Length(Number, 0, 21600), - $position: Number, - $permission_overwrites: [ - { - id: String, - type: new Length(Number, 0, 1), // either 0 (role) or 1 (member) - allow: BigInt, - deny: BigInt - } - ], - $parent_id: String, - $rtc_region: String, - $default_auto_archive_duration: Number, - $id: String, // kept for backwards compatibility does nothing (need for guild create) - $nsfw: Boolean -}; - -export const DmChannelCreateSchema = { - $name: String, - recipients: new Length([String], 1, 10) -}; - -export interface DmChannelCreateSchema { - name?: string; - recipients: string[]; -} - -export interface ChannelModifySchema { - name: string; - type: number; - topic?: string; - bitrate?: number; - user_limit?: number; - rate_limit_per_user?: number; - position?: number; - permission_overwrites?: { - id: string; - type: number; - allow: bigint; - deny: bigint; - }[]; - parent_id?: string; - id?: string; // is not used (only for guild create) - nsfw?: boolean; - rtc_region?: string; - default_auto_archive_duration?: number; -} - -export const ChannelGuildPositionUpdateSchema = [ - { - id: String, - $position: Number - } -]; - -export type ChannelGuildPositionUpdateSchema = { - id: string; - position?: number; -}[]; diff --git a/api/src/schema/Emoji.ts b/api/src/schema/Emoji.ts deleted file mode 100644 index 0406919c..00000000 --- a/api/src/schema/Emoji.ts +++ /dev/null @@ -1,13 +0,0 @@ -// https://discord.com/developers/docs/resources/emoji - -export const EmojiCreateSchema = { - name: String, //name of the emoji - image: String, // image data the 128x128 emoji image uri - $roles: Array //roles allowed to use this emoji -}; - -export interface EmojiCreateSchema { - name: string; // name of the emoji - image: string; // image data the 128x128 emoji image uri - roles?: string[]; //roles allowed to use this emoji -} diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts deleted file mode 100644 index 29c78ab0..00000000 --- a/api/src/schema/Guild.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Channel } from "@fosscord/util"; -import { Length } from "../util/instanceOf"; -import { ChannelModifySchema } from "./Channel"; - -export const GuildCreateSchema = { - name: new Length(String, 2, 100), - $region: String, // auto complete voice region of the user - $icon: String, - $channels: [ChannelModifySchema], - $guild_template_code: String, - $system_channel_id: String, - $rules_channel_id: String -}; - -export interface GuildCreateSchema { - name: string; - region?: string; - icon?: string; - channels?: ChannelModifySchema[]; - guild_template_code?: string; - system_channel_id?: string; - rules_channel_id?: string; -} - -export const GuildUpdateSchema = { - ...GuildCreateSchema, - name: undefined, - $name: new Length(String, 2, 100), - $banner: String, - $splash: String, - $description: String, - $features: [String], - $icon: String, - $verification_level: Number, - $default_message_notifications: Number, - $system_channel_flags: Number, - $system_channel_id: String, - $explicit_content_filter: Number, - $public_updates_channel_id: String, - $afk_timeout: Number, - $afk_channel_id: String, - $preferred_locale: String -}; -// @ts-ignore -delete GuildUpdateSchema.$channels; - -export interface GuildUpdateSchema extends Omit { - banner?: string; - splash?: string; - description?: string; - features?: string[]; - verification_level?: number; - default_message_notifications?: number; - system_channel_flags?: number; - explicit_content_filter?: number; - public_updates_channel_id?: string; - afk_timeout?: number; - afk_channel_id?: string; - preferred_locale?: string; -} - -export const GuildTemplateCreateSchema = { - name: String, - $avatar: String -}; - -export interface GuildTemplateCreateSchema { - name: string; - avatar?: string; -} - -export const GuildUpdateWelcomeScreenSchema = { - $welcome_channels: [ - { - channel_id: String, - description: String, - $emoji_id: String, - emoji_name: String - } - ], - $enabled: Boolean, - $description: new Length(String, 0, 140) -}; - -export interface GuildUpdateWelcomeScreenSchema { - welcome_channels?: { - channel_id: string; - description: string; - emoji_id?: string; - emoji_name: string; - }[]; - enabled?: boolean; - description?: string; -} - -export const VoiceStateUpdateSchema = { - channel_id: String, // Snowflake - $suppress: Boolean, - $request_to_speak_timestamp: String // ISO8601 timestamp -}; - -export interface VoiceStateUpdateSchema { - channel_id: string; // Snowflake - suppress?: boolean; - request_to_speak_timestamp?: string // ISO8601 timestamp -} diff --git a/api/src/schema/Invite.ts b/api/src/schema/Invite.ts deleted file mode 100644 index da6192bc..00000000 --- a/api/src/schema/Invite.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const InviteCreateSchema = { - $target_user_id: String, - $target_type: String, - $validate: String, //? wtf is this - $max_age: Number, - $max_uses: Number, - $temporary: Boolean, - $unique: Boolean, - $target_user: String, - $target_user_type: Number -}; -export interface InviteCreateSchema { - target_user_id?: string; - target_type?: string; - validate?: string; //? wtf is this - max_age?: number; - max_uses?: number; - temporary?: boolean; - unique?: boolean; - target_user?: string; - target_user_type?: number; -} diff --git a/api/src/schema/Member.ts b/api/src/schema/Member.ts deleted file mode 100644 index 607d0a06..00000000 --- a/api/src/schema/Member.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const MemberCreateSchema = { - id: String, - nick: String, - guild_id: String, - joined_at: Date -}; - -export interface MemberCreateSchema { - id: string; - nick: string; - guild_id: string; - joined_at: Date; -} - -export const MemberNickChangeSchema = { - nick: String -}; - -export interface MemberNickChangeSchema { - nick: string; -} - -export const MemberChangeSchema = { - $roles: [String] -}; - -export interface MemberChangeSchema { - roles?: string[]; -} diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts deleted file mode 100644 index d39f685a..00000000 --- a/api/src/schema/Message.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Embed } from "@fosscord/util"; -import { Length } from "../util/instanceOf"; - -export const EmbedImage = { - $url: String, - $width: Number, - $height: Number -}; - -const embed = { - $title: new Length(String, 0, 256), //title of embed - $type: String, // type of embed (always "rich" for webhook embeds) - $description: new Length(String, 0, 2048), // description of embed - $url: String, // url of embed - $timestamp: String, // ISO8601 timestamp - $color: Number, // color code of the embed - $footer: { - text: new Length(String, 0, 2048), - icon_url: String, - proxy_icon_url: String - }, // footer object footer information - $image: EmbedImage, // image object image information - $thumbnail: EmbedImage, // thumbnail object thumbnail information - $video: EmbedImage, // video object video information - $provider: { - name: String, - url: String - }, // provider object provider information - $author: { - name: new Length(String, 0, 256), - url: String, - icon_url: String, - proxy_icon_url: String - }, // author object author information - $fields: new Length( - [ - { - name: new Length(String, 0, 256), - value: new Length(String, 0, 1024), - $inline: Boolean - } - ], - 0, - 25 - ) -}; - -export const MessageCreateSchema = { - $content: new Length(String, 0, 2000), - $nonce: String, - $tts: Boolean, - $flags: String, - $embed: embed, - // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) - // $embeds: [embed], - $allowed_mentions: { - $parse: [String], - $roles: [String], - $users: [String], - $replied_user: Boolean - }, - $message_reference: { - message_id: String, - channel_id: String, - $guild_id: String, - $fail_if_not_exists: Boolean - }, - $payload_json: String, - $file: Object -}; - -export interface MessageCreateSchema { - content?: string; - nonce?: string; - tts?: boolean; - flags?: string; - embed?: Embed & { timestamp?: string }; - allowed_mentions?: { - parse?: string[]; - roles?: string[]; - users?: string[]; - replied_user?: boolean; - }; - message_reference?: { - message_id: string; - channel_id: string; - guild_id?: string; - fail_if_not_exists?: boolean; - }; - payload_json?: string; - file?: any; -} diff --git a/api/src/schema/Roles.ts b/api/src/schema/Roles.ts deleted file mode 100644 index e1a34ae8..00000000 --- a/api/src/schema/Roles.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const RoleModifySchema = { - $name: String, - $permissions: BigInt, - $color: Number, - $hoist: Boolean, // whether the role should be displayed separately in the sidebar - $mentionable: Boolean, // whether the role should be mentionable - $position: Number -}; - -export interface RoleModifySchema { - name?: string; - permissions?: bigint; - color?: number; - hoist?: boolean; // whether the role should be displayed separately in the sidebar - mentionable?: boolean; // whether the role should be mentionable - position?: number; -} - -export const RolePositionUpdateSchema = [ - { - id: String, - position: Number - } -]; - -export type RolePositionUpdateSchema = { - id: string; - position: number; -}[]; diff --git a/api/src/schema/Template.ts b/api/src/schema/Template.ts deleted file mode 100644 index 88e36c53..00000000 --- a/api/src/schema/Template.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const TemplateCreateSchema = { - name: String, - $description: String, -}; - -export interface TemplateCreateSchema { - name: string; - description?: string; -} - -export const TemplateModifySchema = { - name: String, - $description: String, -}; - -export interface TemplateModifySchema { - name: string; - description?: string; -} diff --git a/api/src/schema/User.ts b/api/src/schema/User.ts deleted file mode 100644 index 0d094b9e..00000000 --- a/api/src/schema/User.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { UserSettings } from "../../../util/dist"; -import { Length } from "../util/instanceOf"; - -export const UserModifySchema = { - $username: new Length(String, 2, 32), - $avatar: String, - $bio: new Length(String, 0, 190), - $accent_color: Number, - $banner: String, - $password: String, - $new_password: String, - $code: String // 2fa code -}; - -export interface UserModifySchema { - username?: string; - avatar?: string | null; - bio?: string; - accent_color?: number | null; - banner?: string | null; - password?: string; - new_password?: string; - code?: string; -} - -export const UserSettingsSchema = { - $afk_timeout: Number, - $allow_accessibility_detection: Boolean, - $animate_emoji: Boolean, - $animate_stickers: Number, - $contact_sync_enabled: Boolean, - $convert_emoticons: Boolean, - $custom_status: { - $emoji_id: String, - $emoji_name: String, - $expires_at: Number, - $text: String - }, - $default_guilds_restricted: Boolean, - $detect_platform_accounts: Boolean, - $developer_mode: Boolean, - $disable_games_tab: Boolean, - $enable_tts_command: Boolean, - $explicit_content_filter: Number, - $friend_source_flags: { - all: Boolean - }, - $gateway_connected: Boolean, - $gif_auto_play: Boolean, - $guild_folders: [ - { - color: Number, - guild_ids: [String], - id: Number, - name: String - } - ], - $guild_positions: [String], - $inline_attachment_media: Boolean, - $inline_embed_media: Boolean, - $locale: String, - $message_display_compact: Boolean, - $native_phone_integration_enabled: Boolean, - $render_embeds: Boolean, - $render_reactions: Boolean, - $restricted_guilds: [String], - $show_current_game: Boolean, - $status: String, - $stream_notifications_enabled: Boolean, - $theme: String, - $timezone_offset: Number -}; - -export interface UserSettingsSchema extends UserSettings {} diff --git a/api/src/schema/Widget.ts b/api/src/schema/Widget.ts deleted file mode 100644 index d37a38de..00000000 --- a/api/src/schema/Widget.ts +++ /dev/null @@ -1,10 +0,0 @@ -// https://discord.com/developers/docs/resources/guild#guild-widget-object -export const WidgetModifySchema = { - enabled: Boolean, // whether the widget is enabled - channel_id: String // the widget channel id -}; - -export interface WidgetModifySchema { - enabled: boolean; // whether the widget is enabled - channel_id: string; // the widget channel id -} diff --git a/api/src/schema/index.ts b/api/src/schema/index.ts deleted file mode 100644 index b5f38a2f..00000000 --- a/api/src/schema/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from "./Ban"; -export * from "./Channel"; -export * from "./Emoji"; -export * from "./Guild"; -export * from "./Invite"; -export * from "./Member"; -export * from "./Message"; -export * from "./Roles"; -export * from "./Template"; -export * from "./User"; -export * from "./Widget"; diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts index fea553bc..f8230124 100644 --- a/api/src/util/Message.ts +++ b/api/src/util/Message.ts @@ -22,7 +22,7 @@ import { import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; import cheerio from "cheerio"; -import { MessageCreateSchema } from "../schema/Message"; +import { MessageCreateSchema } from "../routes/channels/#channel_id/messages"; // TODO: check webhook, application, system author diff --git a/api/src/util/Voice.ts b/api/src/util/Voice.ts index 087bdfa8..f06b1aaa 100644 --- a/api/src/util/Voice.ts +++ b/api/src/util/Voice.ts @@ -1,32 +1,32 @@ -import {Config} from "@fosscord/util"; -import {distanceBetweenLocations, IPAnalysis} from "./ipAddress"; +import { Config } from "@fosscord/util"; +import { distanceBetweenLocations, IPAnalysis } from "./ipAddress"; export async function getVoiceRegions(ipAddress: string, vip: boolean) { - const regions = Config.get().regions; - const availableRegions = regions.available.filter(ar => vip ? true : !ar.vip); - let optimalId = regions.default + const regions = Config.get().regions; + const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip)); + let optimalId = regions.default; - if(!regions.useDefaultAsOptimal) { - const clientIpAnalysis = await IPAnalysis(ipAddress) + if (!regions.useDefaultAsOptimal) { + const clientIpAnalysis = await IPAnalysis(ipAddress); - let min = Number.POSITIVE_INFINITY + let min = Number.POSITIVE_INFINITY; - for (let ar of availableRegions) { - //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call - const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint))) + for (let ar of availableRegions) { + //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call + const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint))); - if(dist < min) { - min = dist - optimalId = ar.id - } - } - } + if (dist < min) { + min = dist; + optimalId = ar.id; + } + } + } - return availableRegions.map(ar => ({ - id: ar.id, - name: ar.name, - custom: ar.custom, - deprecated: ar.deprecated, - optimal: ar.id === optimalId - })) -} \ No newline at end of file + return availableRegions.map((ar) => ({ + id: ar.id, + name: ar.name, + custom: ar.custom, + deprecated: ar.deprecated, + optimal: ar.id === optimalId + })); +} diff --git a/api/src/util/VoiceState.ts b/api/src/util/VoiceState.ts deleted file mode 100644 index 07022ec9..00000000 --- a/api/src/util/VoiceState.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; -import { VoiceStateUpdateSchema } from "../schema"; - - -//TODO need more testing when community guild and voice stage channel are working -export async function updateVoiceState(vsuSchema: VoiceStateUpdateSchema, guildId: string, userId: string, targetUserId?: string) { - const perms = await getPermission(userId, guildId, vsuSchema.channel_id); - - /* - From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state - You must have the MUTE_MEMBERS permission to unsuppress yourself. You can always suppress yourself. - You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. - */ - if (targetUserId !== undefined || (vsuSchema.suppress !== undefined && !vsuSchema.suppress)) { - perms.hasThrow("MUTE_MEMBERS"); - } - if (vsuSchema.request_to_speak_timestamp !== undefined && vsuSchema.request_to_speak_timestamp !== "") { - perms.hasThrow("REQUEST_TO_SPEAK") - } - - if (!targetUserId) { - targetUserId = userId; - } else { - if (vsuSchema.suppress !== undefined && vsuSchema.suppress) - vsuSchema.request_to_speak_timestamp = "" //Need to check if empty string is the right value - } - - //TODO assumed that empty string means clean, need to test if it's right - let voiceState - try { - voiceState = await VoiceState.findOneOrFail({ - guild_id: guildId, - channel_id: vsuSchema.channel_id, - user_id: targetUserId - }); - } catch (error) { - throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - } - - voiceState.assign(vsuSchema); - const channel = await Channel.findOneOrFail({ guild_id: guildId, id: vsuSchema.channel_id }) - if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { - throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; - } - - await Promise.all([ - voiceState.save(), - emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voiceState, - guild_id: guildId - } as VoiceStateUpdateEvent)]); - return; -} \ No newline at end of file diff --git a/api/src/util/index.ts b/api/src/util/index.ts index 43481289..c98784a4 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -8,4 +8,3 @@ export * from "./RandomInviteID"; export * from "./route"; export * from "./String"; export * from "./Voice"; -export * from "./VoiceState"; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 0302f3ec..f618a630 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -5,10 +5,20 @@ import path from "path"; import Ajv from "ajv"; import { AnyValidateFunction } from "ajv/dist/core"; import { FieldErrors } from ".."; +import addFormats from "ajv-formats"; const SchemaPath = path.join(__dirname, "..", "..", "assets", "schemas.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); -export const ajv = new Ajv({ allErrors: true, parseDate: true, allowDate: true, schemas, messages: true }); +export const ajv = new Ajv({ + allErrors: true, + parseDate: true, + allowDate: true, + schemas, + messages: true, + strict: true, + strictRequired: true +}); +addFormats(ajv); declare global { namespace Express { @@ -19,7 +29,7 @@ declare global { } export type RouteSchema = string; // typescript interface name -export type RouteResponse = { status: number; body?: RouteSchema; headers?: Record }; +export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record }; export interface RouteOptions { permission?: PermissionResolvable; -- cgit 1.5.1 From e32bb24fa84e5942168433b16f3b0d86aa455a99 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 00:00:11 +0200 Subject: :bug: fix invites: ajv doesn't treat null as undefined --- api/assets/schemas.json | 592 +++++++++++-------------- api/scripts/generate_body_schema.ts | 2 +- api/src/routes/channels/#channel_id/invites.ts | 4 +- api/src/util/route.ts | 1 - 4 files changed, 266 insertions(+), 333 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 59ab4c04..ac7df859 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -177,7 +177,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -274,17 +282,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -499,7 +496,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -596,17 +601,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -718,10 +712,16 @@ "type": "string" }, "target_type": { - "type": "string" + "type": [ + "null", + "string" + ] }, "validate": { - "type": "string" + "type": [ + "null", + "string" + ] }, "max_age": { "type": "integer" @@ -774,7 +774,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -871,17 +879,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1028,7 +1025,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1125,17 +1130,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1285,7 +1279,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1382,17 +1384,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1551,7 +1542,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1648,17 +1647,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1810,7 +1798,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1907,17 +1903,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2064,7 +2049,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -2161,17 +2154,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2330,7 +2312,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -2427,17 +2417,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2606,7 +2585,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -2703,17 +2690,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2915,7 +2891,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3012,17 +2996,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3169,7 +3142,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3266,17 +3247,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3423,7 +3393,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3520,17 +3498,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3689,7 +3656,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3786,17 +3761,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3950,7 +3914,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4047,17 +4019,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4207,7 +4168,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4304,17 +4273,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4464,7 +4422,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4561,17 +4527,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4717,7 +4672,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4814,17 +4777,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4990,7 +4942,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5087,17 +5047,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -5270,7 +5219,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5367,17 +5324,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -5528,7 +5474,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5625,17 +5579,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -5785,7 +5728,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5882,17 +5833,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6045,7 +5985,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6142,17 +6090,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6266,17 +6203,26 @@ "type": "string" }, "avatar": { - "type": "string" + "type": [ + "null", + "string" + ] }, "bio": { "maxLength": 1024, "type": "string" }, "accent_color": { - "type": "integer" + "type": [ + "null", + "integer" + ] }, "banner": { - "type": "string" + "type": [ + "null", + "string" + ] }, "password": { "type": "string" @@ -6320,7 +6266,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6417,17 +6371,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6574,7 +6517,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6671,17 +6622,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6832,7 +6772,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6929,17 +6877,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -7273,7 +7210,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -7370,17 +7315,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts index c406cab8..316e5a69 100644 --- a/api/scripts/generate_body_schema.ts +++ b/api/scripts/generate_body_schema.ts @@ -15,7 +15,7 @@ const settings: TJS.PartialArgs = { defaultProps: false }; const compilerOptions: TJS.CompilerOptions = { - strictNullChecks: false + strictNullChecks: true }; const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"]; diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 39263185..2edb4fc2 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -9,8 +9,8 @@ const router: Router = Router(); export interface InviteCreateSchema { target_user_id?: string; - target_type?: string; - validate?: string; //? wtf is this + target_type?: string | null; + validate?: string | null; // ? what is this max_age?: number; max_uses?: number; temporary?: boolean; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index f618a630..6cd8f622 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -55,7 +55,6 @@ export function route(opts: RouteOptions) { if (opts.permission) { const required = new Permissions(opts.permission); const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); - console.log(required.bitfield, permission.bitfield, permission.bitfield & required.bitfield); // bitfield comparison: check if user lacks certain permission if (!permission.has(required)) { -- cgit 1.5.1 From 4f1b926d097c68b6c73677b710c9731ddc307336 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 01:11:03 +0200 Subject: :bug: fix dm #321 --- api/src/routes/channels/#channel_id/permissions.ts | 4 +- api/src/routes/users/@me/channels.ts | 16 ++++---- api/tests/automatic.test.js | 2 + gateway/src/listener/listener.ts | 2 +- gateway/src/opcodes/Identify.ts | 43 +++++++++++++++------- util/src/entities/Channel.ts | 12 +++--- util/src/util/Permissions.ts | 2 +- 7 files changed, 50 insertions(+), 31 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 827e46f2..959ab8e0 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -35,7 +35,7 @@ router.put( allow: body.allow, deny: body.deny }; - channel.permission_overwrites.push(overwrite); + channel.permission_overwrites!.push(overwrite); } overwrite.allow = body.allow; overwrite.deny = body.deny; @@ -60,7 +60,7 @@ router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (re const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.permission_overwrites = channel.permission_overwrites.filter((x) => x.id === overwrite_id); + channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id); await Promise.all([ channel.save(), diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 5515a217..da33f204 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -4,11 +4,6 @@ import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; import { In } from "typeorm"; -export interface DmChannelCreateSchema { - name?: string; - recipients: string[]; -} - const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { @@ -17,12 +12,17 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.json(recipients.map((x) => x.channel)); }); +export interface DmChannelCreateSchema { + name?: string; + recipients: string[]; +} + router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - const recipients = await User.find({ id: In(body.recipients) }); + const recipients = await User.find({ where: body.recipients.map((x) => ({ id: x })) }); if (recipients.length !== body.recipients.length) { throw new HTTPError("Recipient/s not found"); @@ -34,10 +34,10 @@ router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, const channel = await new Channel({ name, type, - owner_id: req.user_id, + // owner_id only for group dm channels created_at: new Date(), last_message_id: null, - recipients: [...body.recipients.map((x) => new Recipient({ id: x })), new Recipient({ id: req.user_id })] + recipients: [...body.recipients.map((x) => new Recipient({ user_id: x })), new Recipient({ user_id: req.user_id })] }).save(); await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); diff --git a/api/tests/automatic.test.js b/api/tests/automatic.test.js index e69de29b..2d0a9fcb 100644 --- a/api/tests/automatic.test.js +++ b/api/tests/automatic.test.js @@ -0,0 +1,2 @@ +// TODO: check every route based on route() paramters: https://github.com/fosscord/fosscord-server/issues/308 +// TODO: check every route with different database engine diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 7152c74b..ef3dd890 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -70,7 +70,7 @@ export async function setupListener(this: WebSocket) { for (const channel of guild.channels) { if ( x - .overwriteChannel(channel.permission_overwrites) + .overwriteChannel(channel.permission_overwrites!) .has("VIEW_CHANNEL") ) { this.events[channel.id] = await listenEvent( diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 04a6c84c..88c9b942 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -14,6 +14,8 @@ import { dbConnection, PublicMemberProjection, PublicMember, + ChannelType, + PublicUser, } from "@fosscord/util"; import { setupListener } from "../listener/listener"; import { IdentifySchema } from "../schema/Identify"; @@ -57,6 +59,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { return this.close(CLOSECODES.Invalid_shard); } } + var users: PublicUser[] = []; const members = await Member.find({ where: { id: this.user_id }, @@ -85,12 +88,36 @@ export async function onIdentify(this: WebSocket, data: Payload) { const recipients = await Recipient.find({ where: { user_id: this.user_id }, - relations: ["channel", "channel.recipients"], + relations: ["channel", "channel.recipients", "channel.recipients.user"], + // TODO: public user selection + }); + const channels = recipients.map((x) => { + // @ts-ignore + x.channel.recipients = x.channel.recipients?.map((x) => x.user); + // @ts-ignore + users = users.concat(x.channel.recipients); + if (x.channel.type === ChannelType.DM) { + x.channel.recipients = [ + // @ts-ignore + x.channel.recipients.find((x) => x.id !== this.user_id), + ]; + } + return x.channel; }); - const channels = recipients.map((x) => x.channel); const user = await User.findOneOrFail({ id: this.user_id }); if (!user) return this.close(CLOSECODES.Authentication_failed); + const public_user = { + username: user.username, + discriminator: user.discriminator, + id: user.id, + public_flags: user.public_flags, + avatar: user.avatar, + bot: user.bot, + bio: user.bio, + }; + users.push(public_user); + const session_id = genSessionId(); this.session_id = session_id; //Set the session of the WebSocket object const session = new Session({ @@ -108,16 +135,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { //We save the session and we delete it when the websocket is closed await session.save(); - const public_user = { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - bot: user.bot, - bio: user.bio, - }; - const privateUser = { avatar: user.avatar, mobile: user.mobile, @@ -180,7 +197,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? - users: [public_user].unique(), // TODO + users: users.unique(), // TODO merged_members: merged_members, // shard // TODO: only for bots sharding // application // TODO for applications diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 592b0b83..fc954f63 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -28,8 +28,8 @@ export class Channel extends BaseClass { @Column() created_at: Date; - @Column() - name: string; + @Column({ nullable: true }) + name?: string; @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; @@ -76,11 +76,11 @@ export class Channel extends BaseClass { @Column({ nullable: true }) default_auto_archive_duration?: number; - @Column() - position: number; + @Column({ nullable: true }) + position?: number; - @Column({ type: "simple-json" }) - permission_overwrites: ChannelPermissionOverwrite[]; + @Column({ type: "simple-json", nullable: true }) + permission_overwrites?: ChannelPermissionOverwrite[]; @Column({ nullable: true }) video_quality_mode?: number; diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts index 628a495d..9d87253a 100644 --- a/util/src/util/Permissions.ts +++ b/util/src/util/Permissions.ts @@ -242,7 +242,7 @@ export async function getPermission( }); } - let recipient_ids: any = channel?.recipients?.map((x) => x.id); + let recipient_ids: any = channel?.recipients?.map((x) => x.user_id); if (!recipient_ids?.length) recipient_ids = null; // TODO: remove guild.roles and convert recipient_ids to recipients -- cgit 1.5.1 From 9e3bc94e9de3ed0487f27658a90468ecddb6b926 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:22:41 +0200 Subject: :bug: fix relationship --- api/src/routes/users/@me/relationships.ts | 108 +++++++++++++++--------------- gateway/src/opcodes/Identify.ts | 7 +- util/src/entities/Relationship.ts | 30 +++++++-- util/src/interfaces/Event.ts | 14 ++-- 4 files changed, 94 insertions(+), 65 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index cc264f3f..58d2e481 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -31,7 +31,7 @@ router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request return await updateRelationship( req, res, - await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), + await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }), req.body.type ); }); @@ -46,7 +46,7 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, req, res, await User.findOneOrFail({ - relations: ["relationships"], + relations: ["relationships", "relationships.to"], select: userProjection, where: req.body as { discriminator: string; username: string } }), @@ -61,37 +61,40 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); - const relationship = user.relationships.find((x) => x.id === id); - const friendRequest = friend.relationships.find((x) => x.id === req.user_id); + const relationship = user.relationships.find((x) => x.to_id === id); + const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + if (!relationship) throw new HTTPError("You are not friends with the user", 404); if (relationship?.type === RelationshipType.blocked) { // unblock user - user.relationships.remove(relationship); await Promise.all([ - user.save(), - emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent) + Relationship.delete({ id: relationship.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + user_id: req.user_id, + data: relationship.toPublicRelationship() + } as RelationshipRemoveEvent) ]); return res.sendStatus(204); } - if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); - if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - - user.relationships.remove(relationship); - friend.relationships.remove(friendRequest); + if (friendRequest && friendRequest.type !== RelationshipType.blocked) { + await Promise.all([ + Relationship.delete({ id: friendRequest.id }), + await emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest.toPublicRelationship(), + user_id: id + } as RelationshipRemoveEvent) + ]); + } await Promise.all([ - user.save(), - friend.save(), + Relationship.delete({ id: relationship.id }), emitEvent({ event: "RELATIONSHIP_REMOVE", - data: relationship, + data: relationship.toPublicRelationship(), user_id: req.user_id - } as RelationshipRemoveEvent), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest, - user_id: id } as RelationshipRemoveEvent) ]); @@ -104,44 +107,40 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail({ id: req.user_id }, { relations: ["relationships"], select: userProjection }); + const user = await User.findOneOrFail( + { id: req.user_id }, + { relations: ["relationships", "relationships.to"], select: userProjection } + ); - var relationship = user.relationships.find((x) => x.id === id); - const friendRequest = friend.relationships.find((x) => x.id === req.user_id); + var relationship = user.relationships.find((x) => x.to_id === id); + const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); // TODO: you can add infinitely many blocked users (should this be prevented?) if (type === RelationshipType.blocked) { if (relationship) { if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); relationship.type = RelationshipType.blocked; + await relationship.save(); } else { - relationship = new Relationship({ id, type: RelationshipType.blocked }); - user.relationships.push(relationship); + relationship = await new Relationship({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save(); } if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - friend.relationships.remove(friendRequest); await Promise.all([ - user.save(), + Relationship.delete({ id: friendRequest.id }), emitEvent({ event: "RELATIONSHIP_REMOVE", - data: friendRequest, + data: friendRequest.toPublicRelationship(), user_id: id } as RelationshipRemoveEvent) ]); } - await Promise.all([ - user.save(), - emitEvent({ - event: "RELATIONSHIP_ADD", - data: { - ...relationship, - user: { ...friend } - }, - user_id: req.user_id - } as RelationshipAddEvent) - ]); + await emitEvent({ + event: "RELATIONSHIP_ADD", + data: relationship.toPublicRelationship(), + user_id: req.user_id + } as RelationshipAddEvent); return res.sendStatus(204); } @@ -149,40 +148,43 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const { maxFriends } = Config.get().limits.user; if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, id: req.user_id }); - var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing, id }); + var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend }); + var outgoing_relationship = new Relationship({ + nickname: undefined, + type: RelationshipType.outgoing, + to: friend, + from: user + }); if (friendRequest) { if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); + if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); // accept friend request incoming_relationship = friendRequest; incoming_relationship.type = RelationshipType.friends; - outgoing_relationship.type = RelationshipType.friends; - } else friend.relationships.push(incoming_relationship); + } if (relationship) { if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - } else user.relationships.push(outgoing_relationship); + outgoing_relationship = relationship; + outgoing_relationship.type = RelationshipType.friends; + } await Promise.all([ - user.save(), - friend.save(), + incoming_relationship.save(), + outgoing_relationship.save(), emitEvent({ event: "RELATIONSHIP_ADD", - data: { - ...outgoing_relationship, - user: { ...friend } - }, + data: outgoing_relationship.toPublicRelationship(), user_id: req.user_id } as RelationshipAddEvent), emitEvent({ event: "RELATIONSHIP_ADD", data: { - ...incoming_relationship, - should_notify: true, - user: { ...user } + ...incoming_relationship.toPublicRelationship(), + should_notify: true }, user_id: id } as RelationshipAddEvent) diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 88c9b942..9eb4cd32 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -104,7 +104,10 @@ export async function onIdentify(this: WebSocket, data: Payload) { } return x.channel; }); - const user = await User.findOneOrFail({ id: this.user_id }); + const user = await User.findOneOrFail({ + where: { id: this.user_id }, + relations: ["relationships", "relationships.to"], + }); if (!user) return this.close(CLOSECODES.Authentication_failed); const public_user = { @@ -171,7 +174,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { }), guild_experiments: [], // TODO geo_ordered_rtc_regions: [], // TODO - relationships: user.relationships, + relationships: user.relationships.map((x) => x.toPublicRelationship()), read_state: { // TODO entries: [], diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts index 5935f5b6..61b3ac82 100644 --- a/util/src/entities/Relationship.ts +++ b/util/src/entities/Relationship.ts @@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; @@ -10,18 +10,36 @@ export enum RelationshipType { } @Entity("relationships") +@Index(["from_id", "to_id"], { unique: true }) export class Relationship extends BaseClass { - @Column({ nullable: true }) - @RelationId((relationship: Relationship) => relationship.user) - user_id: string; + @Column({}) + @RelationId((relationship: Relationship) => relationship.from) + from_id: string; - @JoinColumn({ name: "user_id" }) + @JoinColumn({ name: "from_id" }) @ManyToOne(() => User) - user: User; + from: User; + + @Column({}) + @RelationId((relationship: Relationship) => relationship.to) + to_id: string; + + @JoinColumn({ name: "to_id" }) + @ManyToOne(() => User) + to: User; @Column({ nullable: true }) nickname?: string; @Column({ type: "simple-enum", enum: RelationshipType }) type: RelationshipType; + + toPublicRelationship() { + return { + id: this.to?.id || this.to_id, + type: this.type, + nickname: this.nickname, + user: this.to?.toPublicUser(), + }; + } } diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index 5c2a01b0..aff50300 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -10,7 +10,7 @@ import { VoiceState } from "../entities/VoiceState"; import { ApplicationCommand } from "../entities/Application"; import { Interaction } from "./Interaction"; import { ConnectedAccount } from "../entities/ConnectedAccount"; -import { Relationship } from "../entities/Relationship"; +import { Relationship, RelationshipType } from "../entities/Relationship"; import { Presence } from "./Presence"; export interface Event { @@ -28,6 +28,12 @@ export interface InvalidatedEvent extends Event { event: "INVALIDATED"; } +export interface PublicRelationship { + id: string; + user: PublicUser; + type: RelationshipType; +} + // ! END Custom Events that shouldn't get sent to the client but processed by the server export interface ReadyEventData { @@ -72,7 +78,7 @@ export interface ReadyEventData { guild_join_requests?: any[]; // ? what is this? this is new shard?: [number, number]; user_settings?: UserSettings; - relationships?: Relationship[]; // TODO + relationships?: PublicRelationship[]; // TODO read_state: { entries: any[]; // TODO partial: boolean; @@ -412,7 +418,7 @@ export interface MessageAckEvent extends Event { export interface RelationshipAddEvent extends Event { event: "RELATIONSHIP_ADD"; - data: Relationship & { + data: PublicRelationship & { should_notify?: boolean; user: PublicUser; }; @@ -420,7 +426,7 @@ export interface RelationshipAddEvent extends Event { export interface RelationshipRemoveEvent extends Event { event: "RELATIONSHIP_REMOVE"; - data: Omit; + data: Omit; } export type EventData = -- cgit 1.5.1 From 9b4457424df8258d7ded0bebe1b321f5e69b7b8e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:22:59 +0200 Subject: :bug: fix In() query --- api/src/routes/auth/register.ts | 2 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- util/src/entities/User.ts | 14 +++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index e0af1d6d..e70e01ed 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -137,7 +137,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re if (!register.allowMultipleAccounts) { // TODO: check if fingerprint was eligible generated - const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); + const exists = await User.findOne({ where: { fingerprints: fingerprint } }); if (exists) { throw FieldErrors({ diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 6b2902d9..5c549262 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -132,7 +132,7 @@ router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Reque await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); - const roles = await Role.find({ guild_id, id: In(body.map((x) => x.id)) }); + const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) }); await Promise.all( roles.map((x) => diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 9394f9e8..736704f8 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -106,7 +106,7 @@ export class User extends BaseClass { mfa_enabled: boolean; // if multi factor authentication is enabled @Column() - created_at: Date = new Date(); // registration date + created_at: Date; // registration date @Column() verified: boolean; // if the user is offically verified @@ -127,7 +127,7 @@ export class User extends BaseClass { public_flags: string; @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.user, { cascade: true }) + @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) @@ -146,6 +146,14 @@ export class User extends BaseClass { @Column({ type: "simple-json" }) settings: UserSettings; + toPublicUser() { + const user: any = {}; + PublicUserProjection.forEach((x) => { + user[x] = this[x]; + }); + return user as PublicUser; + } + static async getPublicUser(user_id: string, opts?: FindOneOptions) { const user = await User.findOne( { id: user_id }, @@ -261,7 +269,7 @@ export class UserFlags extends BitField { SYSTEM: BigInt(1) << BigInt(12), HAS_UNREAD_URGENT_MESSAGES: BigInt(1) << BigInt(13), BUGHUNTER_LEVEL_2: BigInt(1) << BigInt(14), - UNDERAGE_DELETED: BigInt(1) << BigInt(15), + UNDERAGE_DELETED: BigInt(1) << BigInt(15), VERIFIED_BOT: BigInt(1) << BigInt(16), EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), }; -- cgit 1.5.1 From aa1f1a22a4cce0c51d84c10d5a741699f7bbfff2 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 17:32:31 +0200 Subject: Delete attachments of deleted messages, fix #273 --- api/assets/schemas.json | 6 +++++- .../channels/#channel_id/messages/#message_id/index.ts | 7 ++++--- api/src/routes/channels/#channel_id/messages/index.ts | 1 + api/src/util/Attachments.ts | 12 ++++++++++++ api/src/util/cdn.ts | 13 +++++++++++++ cdn/src/util/FileStorage.ts | 1 + 6 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 api/src/util/Attachments.ts (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index ac7df859..94aa0660 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -462,7 +462,11 @@ "payload_json": { "type": "string" }, - "file": {} + "file": {}, + "attachments": { + "type": "array", + "items": {} + } }, "additionalProperties": false, "definitions": { diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index d0f780db..b5220fab 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -3,6 +3,7 @@ import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; import { handleMessage, postHandleMessage } from "@fosscord/api"; import { MessageCreateSchema } from "../index"; +import { deleteMessageAttachments } from "@fosscord/api/util/Attachments"; const router = Router(); // TODO: message content/embed string length limit @@ -11,7 +12,7 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; - const message = await Message.findOneOrFail({ id: message_id, channel_id }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -33,6 +34,7 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE }); await Promise.all([ + await deleteMessageAttachments(message_id, new_message.attachments), //This delete all the attachments not in the array new_message!.save(), await emitEvent({ event: "MESSAGE_UPDATE", @@ -46,8 +48,6 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE return res.json(message); }); -// TODO: delete attachments in message - // permission check only if deletes messagr from other user router.delete("/", route({}), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; @@ -60,6 +60,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => { permission.hasThrow("MANAGE_MESSAGES"); } + await deleteMessageAttachments(message_id); await Message.delete({ id: message_id }); await emitEvent({ diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 11334367..25ecc1c7 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -51,6 +51,7 @@ export interface MessageCreateSchema { }; payload_json?: string; file?: any; + attachments?: any[]; //TODO we should create an interface for attachments } // https://discord.com/developers/docs/resources/channel#create-message diff --git a/api/src/util/Attachments.ts b/api/src/util/Attachments.ts new file mode 100644 index 00000000..addda97f --- /dev/null +++ b/api/src/util/Attachments.ts @@ -0,0 +1,12 @@ +import { Attachment } from "@fosscord/util"; +import { deleteFile } from "@fosscord/api"; +import { URL } from "url"; + +export async function deleteMessageAttachments(messageId: string, keep?: Attachment[]) { + let attachments = await Attachment.find({ message_id: messageId }); + if (keep) + attachments = attachments.filter(x => !keep.map(k => k.id).includes(x.id)); + await Promise.all(attachments.map(a => a.remove())); + + attachments.forEach(a => deleteFile((new URL(a.url)).pathname)); //We don't need to await since this is done on the cdn +} diff --git a/api/src/util/cdn.ts b/api/src/util/cdn.ts index 3c71d980..88b0ea0d 100644 --- a/api/src/util/cdn.ts +++ b/api/src/util/cdn.ts @@ -38,3 +38,16 @@ export async function handleFile(path: string, body?: string): Promise Date: Mon, 13 Sep 2021 17:51:16 +0200 Subject: :art: remove unused imports --- api/src/routes/auth/register.ts | 1 - .../routes/channels/#channel_id/messages/index.ts | 17 +- api/src/routes/channels/#channel_id/permissions.ts | 2 +- api/src/routes/channels/#channel_id/typing.ts | 4 +- api/src/routes/channels/#channel_id/webhooks.ts | 2 +- api/src/routes/guilds/#guild_id/bans.ts | 2 +- api/src/routes/guilds/#guild_id/channels.ts | 2 +- api/src/routes/guilds/#guild_id/index.ts | 2 +- .../guilds/#guild_id/members/#member_id/index.ts | 2 +- api/src/routes/guilds/#guild_id/members/index.ts | 24 +-- api/src/routes/guilds/#guild_id/roles.ts | 3 +- api/src/routes/guilds/#guild_id/vanity-url.ts | 2 +- .../#guild_id/voice-states/#user_id/index.ts | 2 +- api/src/routes/guilds/#guild_id/widget.ts | 5 +- api/src/routes/guilds/index.ts | 3 +- api/src/routes/guilds/templates/index.ts | 3 +- api/src/routes/users/@me/index.ts | 2 +- api/src/util/FieldError.ts | 25 +++ api/src/util/String.ts | 8 +- api/src/util/index.ts | 2 +- api/src/util/instanceOf.ts | 214 --------------------- 21 files changed, 61 insertions(+), 266 deletions(-) create mode 100644 api/src/util/FieldError.ts delete mode 100644 api/src/util/instanceOf.ts (limited to 'api/src') diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index e70e01ed..33f089b2 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -6,7 +6,6 @@ import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import { HTTPError } from "lambert-server"; -import { In } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 11334367..be9a41b1 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,7 +1,7 @@ import { Router, Response, Request } from "express"; import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { instanceOf, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import multer from "multer"; import { sendMessage } from "@fosscord/api"; import { uploadFile } from "@fosscord/api"; @@ -61,17 +61,12 @@ router.get("/", async (req: Request, res: Response) => { if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); + const around = `${req.query.around}`; + const before = `${req.query.before}`; + const after = `${req.query.after}`; + const limit = Number(req.query.limit) || 50; + if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100"); - try { - instanceOf({ $around: String, $after: String, $before: String, $limit: new Length(Number, 1, 100) }, req.query, { - path: "query", - req - }); - } catch (error) { - return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error }); - } - var { around, after, before, limit }: { around?: string; after?: string; before?: string; limit?: number } = req.query; - if (!limit) limit = 50; var halfLimit = Math.floor(limit / 2); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 959ab8e0..bc7ad5b8 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -2,7 +2,7 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index ad973bca..a9dcb315 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -9,14 +9,14 @@ router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, re const user_id = req.user_id; const timestamp = Date.now(); const channel = await Channel.findOneOrFail({ id: channel_id }); - const member = await Member.findOneOrFail({ id: user_id }); + const member = await Member.findOneOrFail({ where: { id: user_id }, relations: ["roles"] }); await emitEvent({ event: "TYPING_START", channel_id: channel_id, data: { // this is the paylod - member: { ...member, roles: member.roles.map((x) => x.id) }, + member: { ...member, roles: member.roles?.map((x) => x.id) }, channel_id, timestamp, user_id, diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index f84dfcc5..7b894455 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { check, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index c7fda9ad..e7d46898 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { getIpAdress, check, route } from "@fosscord/api"; +import { getIpAdress, route } from "@fosscord/api"; export interface BanCreateSchema { delete_message_days?: string; diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index e21327d1..13c6b515 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,7 +1,7 @@ import { Router, Response, Request } from "express"; import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 690d4103..a75d1138 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; import { GuildCreateSchema } from "../index"; diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 1708b7eb..ab489743 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Member, getPermission, Role, GuildMemberUpdateEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 335f21c7..386276c8 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,7 +1,8 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { MoreThan } from "typeorm"; +import { HTTPError } from "lambert-server"; const router = Router(); @@ -11,26 +12,17 @@ const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); - await Member.IsInGuildOrFail(req.user_id, guild_id); - - try { - instanceOf({ $limit: new Length(Number, 1, 1000), $after: String }, req.query, { - path: "query", - req, - ref: { obj: null, key: "" } - }); - } catch (error) { - return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error }); - } - - const { limit, after } = (req.query) as { limit?: number; after?: string }; + const limit = Number(req.query.limit) || 1; + if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); + const after = `${req.query.after}`; const query = after ? { id: MoreThan(after) } : {}; + await Member.IsInGuildOrFail(req.user_id, guild_id); + const members = await Member.find({ where: { guild_id, ...query }, select: PublicMemberProjection, - take: limit || 1, + take: limit, order: { id: "ASC" } }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 5c549262..bac63bd4 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -11,8 +11,7 @@ import { DiscordApiErrors } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; -import { In } from "typeorm"; +import { route } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 9c0989cc..801768fb 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,6 +1,6 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { check, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 3d76938b..f9fbea54 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,5 +1,5 @@ import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index c8caae14..2640618d 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,7 +1,6 @@ import { Request, Response, Router } from "express"; -import { getPermission, Guild } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { Guild } from "@fosscord/util"; +import { route } from "@fosscord/api"; export interface WidgetModifySchema { enabled: boolean; // whether the widget is enabled diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index ba951f96..082f8539 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,7 +1,6 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; import { ChannelModifySchema } from "../channels/#channel_id"; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index d7a42044..eb3867c8 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; export interface GuildTemplateCreateSchema { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 68723374..abab9f5f 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { User, PrivateUserProjection } from "@fosscord/util"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/util/FieldError.ts b/api/src/util/FieldError.ts new file mode 100644 index 00000000..0b3f93d2 --- /dev/null +++ b/api/src/util/FieldError.ts @@ -0,0 +1,25 @@ +import "missing-native-js-functions"; + +export function FieldErrors(fields: Record) { + return new FieldError( + 50035, + "Invalid Form Body", + fields.map(({ message, code }) => ({ + _errors: [ + { + message, + code: code || "BASE_TYPE_INVALID" + } + ] + })) + ); +} + +// TODO: implement Image data type: Data URI scheme that supports JPG, GIF, and PNG formats. An example Data URI format is: data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA +// Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided. + +export class FieldError extends Error { + constructor(public code: string | number, public message: string, public errors?: any) { + super(message); + } +} diff --git a/api/src/util/String.ts b/api/src/util/String.ts index 49fba237..2fe32d2c 100644 --- a/api/src/util/String.ts +++ b/api/src/util/String.ts @@ -1,14 +1,16 @@ import { Request } from "express"; import { ntob } from "./Base64"; -import { FieldErrors } from "./instanceOf"; +import { FieldErrors } from "./FieldError"; +export const EMAIL_REGEX = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { if (str.length < min || str.length > max) { throw FieldErrors({ [key]: { code: "BASE_TYPE_BAD_LENGTH", - message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` }), - }, + message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` }) + } }); } } diff --git a/api/src/util/index.ts b/api/src/util/index.ts index c98784a4..4b1e8e77 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -1,6 +1,6 @@ export * from "./Base64"; export * from "./cdn"; -export * from "./instanceOf"; +export * from "./FieldError"; export * from "./ipAddress"; export * from "./Message"; export * from "./passwordStrength"; diff --git a/api/src/util/instanceOf.ts b/api/src/util/instanceOf.ts deleted file mode 100644 index 4d9034e5..00000000 --- a/api/src/util/instanceOf.ts +++ /dev/null @@ -1,214 +0,0 @@ -// different version of lambert-server instanceOf with discord error format - -import { NextFunction, Request, Response } from "express"; -import { Tuple } from "lambert-server"; -import "missing-native-js-functions"; - -export const OPTIONAL_PREFIX = "$"; -export const EMAIL_REGEX = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - -export function check(schema: any) { - return (req: Request, res: Response, next: NextFunction) => { - try { - const result = instanceOf(schema, req.body, { path: "body", req, ref: { obj: null, key: "" } }); - if (result === true) return next(); - throw result; - } catch (error) { - return res.status(400).json({ code: 50035, message: "Invalid Form Body", success: false, errors: error }); - } - }; -} - -export function FieldErrors(fields: Record) { - return new FieldError( - 50035, - "Invalid Form Body", - fields.map(({ message, code }) => ({ - _errors: [ - { - message, - code: code || "BASE_TYPE_INVALID" - } - ] - })) - ); -} - -// TODO: implement Image data type: Data URI scheme that supports JPG, GIF, and PNG formats. An example Data URI format is: data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA -// Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided. - -export class FieldError extends Error { - constructor(public code: string | number, public message: string, public errors?: any) { - super(message); - } -} - -export class Email { - constructor(public email: string) {} - check() { - return !!this.email.match(EMAIL_REGEX); - } -} - -export class Length { - constructor(public type: any, public min: number, public max: number) {} - - check(value: string) { - if (typeof value === "string" || Array.isArray(value)) return value.length >= this.min && value.length <= this.max; - if (typeof value === "number" || typeof value === "bigint") return value >= this.min && value <= this.max; - return false; - } -} - -export function instanceOf( - type: any, - value: any, - { - path = "", - optional = false, - errors = {}, - req, - ref - }: { path?: string; optional?: boolean; errors?: any; req: Request; ref?: { key: string | number; obj: any } } -): Boolean { - if (!ref) ref = { obj: null, key: "" }; - if (!path) path = "body"; - if (!type) return true; // no type was specified - - try { - if (value == null) { - if (optional) return true; - throw new FieldError("BASE_TYPE_REQUIRED", req.t("common:field.BASE_TYPE_REQUIRED")); - } - - switch (type) { - case String: - value = `${value}`; - ref.obj[ref.key] = value; - if (typeof value === "string") return true; - throw new FieldError("BASE_TYPE_STRING", req.t("common:field.BASE_TYPE_STRING")); - case Number: - value = Number(value); - ref.obj[ref.key] = value; - if (typeof value === "number" && !isNaN(value)) return true; - throw new FieldError("BASE_TYPE_NUMBER", req.t("common:field.BASE_TYPE_NUMBER")); - case BigInt: - try { - value = BigInt(value); - ref.obj[ref.key] = value; - if (typeof value === "bigint") return true; - } catch (error) {} - throw new FieldError("BASE_TYPE_BIGINT", req.t("common:field.BASE_TYPE_BIGINT")); - case Boolean: - if (value == "true") value = true; - if (value == "false") value = false; - ref.obj[ref.key] = value; - if (typeof value === "boolean") return true; - throw new FieldError("BASE_TYPE_BOOLEAN", req.t("common:field.BASE_TYPE_BOOLEAN")); - - case Email: - if (new Email(value).check()) return true; - throw new FieldError("EMAIL_TYPE_INVALID_EMAIL", req.t("common:field.EMAIL_TYPE_INVALID_EMAIL")); - case Date: - value = new Date(value); - ref.obj[ref.key] = value; - // value.getTime() can be < 0, if it is before 1970 - if (!isNaN(value)) return true; - throw new FieldError("DATE_TYPE_PARSE", req.t("common:field.DATE_TYPE_PARSE")); - } - - if (typeof type === "object") { - if (Array.isArray(type)) { - if (!Array.isArray(value)) throw new FieldError("BASE_TYPE_ARRAY", req.t("common:field.BASE_TYPE_ARRAY")); - if (!type.length) return true; // type array didn't specify any type - - return ( - value.every((val, i) => { - errors[i] = {}; - - if ( - instanceOf(type[0], val, { - path: `${path}[${i}]`, - optional, - errors: errors[i], - req, - ref: { key: i, obj: value } - }) === true - ) { - delete errors[i]; - return true; - } - - return false; - }) || errors - ); - } else if (type?.constructor?.name != "Object") { - if (type instanceof Tuple) { - if ((type).types.some((x) => instanceOf(x, value, { path, optional, errors, req, ref }))) return true; - throw new FieldError("BASE_TYPE_CHOICES", req.t("common:field.BASE_TYPE_CHOICES", { types: type.types })); - } else if (type instanceof Length) { - let length = type; - if (instanceOf(length.type, value, { path, optional, req, ref, errors }) !== true) return errors; - let val = ref.obj[ref.key]; - if ((type).check(val)) return true; - throw new FieldError( - "BASE_TYPE_BAD_LENGTH", - req.t("common:field.BASE_TYPE_BAD_LENGTH", { - length: `${type.min} - ${type.max}` - }) - ); - } - try { - if (value instanceof type) return true; - } catch (error) { - throw new FieldError("BASE_TYPE_CLASS", req.t("common:field.BASE_TYPE_CLASS", { type })); - } - } - - if (typeof value !== "object") throw new FieldError("BASE_TYPE_OBJECT", req.t("common:field.BASE_TYPE_OBJECT")); - - const diff = Object.keys(value).missing( - Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)) - ); - - if (diff.length) throw new FieldError("UNKOWN_FIELD", req.t("common:field.UNKOWN_FIELD", { key: diff })); - - return ( - Object.keys(type).every((key) => { - let newKey = key; - const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); - if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); - errors[newKey] = {}; - - if ( - instanceOf(type[key], value[newKey], { - path: `${path}.${newKey}`, - optional: OPTIONAL, - errors: errors[newKey], - req, - ref: { key: newKey, obj: value } - }) === true - ) { - delete errors[newKey]; - return true; - } - - return false; - }) || errors - ); - } else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") { - if (value === type) return true; - throw new FieldError("BASE_TYPE_CONSTANT", req.t("common:field.BASE_TYPE_CONSTANT", { value: type })); - } else if (typeof type === "bigint") { - if (BigInt(value) === type) return true; - throw new FieldError("BASE_TYPE_CONSTANT", req.t("common:field.BASE_TYPE_CONSTANT", { value: type })); - } - - return type == value; - } catch (error) { - let e = error as FieldError; - errors._errors = [{ message: e.message, code: e.code }]; - return errors; - } -} -- cgit 1.5.1 From 8568b3cdce917731f99456601b31228b933fa9ed Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:51:37 +0200 Subject: :bug: fix fosscord.js --- api/src/routes/gateway.ts | 14 ++++++++++++++ gateway/src/opcodes/Identify.ts | 5 +++-- gateway/src/schema/Identify.ts | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/gateway.ts b/api/src/routes/gateway.ts index d4208341..88d9dfda 100644 --- a/api/src/routes/gateway.ts +++ b/api/src/routes/gateway.ts @@ -9,4 +9,18 @@ router.get("/", route({}), (req: Request, res: Response) => { res.json({ url: endpoint || process.env.GATEWAY || "ws://localhost:3002" }); }); +router.get("/bot", route({}), (req: Request, res: Response) => { + const { endpoint } = Config.get().gateway; + res.json({ + url: endpoint || process.env.GATEWAY || "ws://localhost:3002", + shards: 1, + session_start_limit: { + total: 1000, + remaining: 999, + reset_after: 14400000, + max_concurrency: 1 + } + }); +}); + export default router; diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 9eb4cd32..f6a4478f 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -50,12 +50,13 @@ export async function onIdentify(this: WebSocket, data: Payload) { this.shard_id = identify.shard[0]; this.shard_count = identify.shard[1]; if ( - !this.shard_count || - !this.shard_id || + this.shard_count == null || + this.shard_id == null || this.shard_id >= this.shard_count || this.shard_id < 0 || this.shard_count <= 0 ) { + console.log(identify.shard); return this.close(CLOSECODES.Invalid_shard); } } diff --git a/gateway/src/schema/Identify.ts b/gateway/src/schema/Identify.ts index 0835ddc7..6054f2e8 100644 --- a/gateway/src/schema/Identify.ts +++ b/gateway/src/schema/Identify.ts @@ -43,6 +43,7 @@ export const IdentifySchema = { $user_guild_settings_version: Number, }, $v: Number, + $version: Number, }; export interface IdentifySchema { -- cgit 1.5.1 From b15a93d120e5998eccedba99086c1e539ba5983f Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 19:09:01 +0200 Subject: Emit USER_UPDATE, fix #214 --- api/src/routes/users/@me/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 68723374..a8c0edc4 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; -import { User, PrivateUserProjection } from "@fosscord/util"; -import { check, route } from "@fosscord/api"; +import { User, PrivateUserProjection, emitEvent, UserUpdateEvent } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; const router: Router = Router(); @@ -33,8 +33,16 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await new User({ ...body, id: req.user_id }).save(); - // TODO: dispatch user update event + await new User({ ...body, id: req.user_id }).save(); + + //Need to reload user from db due to https://github.com/typeorm/typeorm/issues/3490 + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: PrivateUserProjection }); + + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user + } as UserUpdateEvent); res.json(user); }); -- cgit 1.5.1 From 68eae50a63df0a84d75c05abce5142a33f4c5bd3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 19:12:19 +0200 Subject: Update index.ts --- api/src/routes/users/@me/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index a8c0edc4..c0002d79 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -37,7 +37,7 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: //Need to reload user from db due to https://github.com/typeorm/typeorm/issues/3490 const user = await User.findOneOrFail({ where: { id: req.user_id }, select: PrivateUserProjection }); - + // TODO: send update member list event in gateway await emitEvent({ event: "USER_UPDATE", user_id: req.user_id, -- cgit 1.5.1 From 7cdcb82ecdf51b2dab5808a89ed50b96af0a60cc Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 19:25:44 +0200 Subject: Fix #356 --- api/assets/schemas.json | 35 +++++++++++++++++++++++++------- api/src/routes/guilds/#guild_id/index.ts | 10 ++++----- api/src/routes/guilds/index.ts | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 94aa0660..3f760c35 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -2548,7 +2548,10 @@ "type": "string" }, "system_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "rules_channel_id": { "type": "string" @@ -2802,13 +2805,22 @@ "type": "object", "properties": { "banner": { - "type": "string" + "type": [ + "null", + "string" + ] }, "splash": { - "type": "string" + "type": [ + "null", + "string" + ] }, "description": { - "type": "string" + "type": [ + "null", + "string" + ] }, "features": { "type": "array", @@ -2829,13 +2841,19 @@ "type": "integer" }, "public_updates_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "afk_timeout": { "type": "integer" }, "afk_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "preferred_locale": { "type": "string" @@ -2854,7 +2872,10 @@ "type": "string" }, "system_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "rules_channel_id": { "type": "string" diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 690d4103..9c67798d 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -9,17 +9,17 @@ import { GuildCreateSchema } from "../index"; const router = Router(); export interface GuildUpdateSchema extends Omit { - banner?: string; - splash?: string; - description?: string; + banner?: string | null; + splash?: string | null; + description?: string | null; features?: string[]; verification_level?: number; default_message_notifications?: number; system_channel_flags?: number; explicit_content_filter?: number; - public_updates_channel_id?: string; + public_updates_channel_id?: string | null; afk_timeout?: number; - afk_channel_id?: string; + afk_channel_id?: string | null; preferred_locale?: string; } diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index ba951f96..674dc16b 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -16,7 +16,7 @@ export interface GuildCreateSchema { icon?: string; channels?: ChannelModifySchema[]; guild_template_code?: string; - system_channel_id?: string; + system_channel_id?: string | null; rules_channel_id?: string; } -- cgit 1.5.1 From 9b10ab2d0b7a7a1c3c9a8b65a02518dffdbf04ba Mon Sep 17 00:00:00 2001 From: uurgothat Date: Mon, 13 Sep 2021 21:22:14 +0300 Subject: add store endpoints --- api/src/routes/store/applications.ts | 11 +++++++++++ api/src/routes/store/skus.ts | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 api/src/routes/store/applications.ts create mode 100644 api/src/routes/store/skus.ts (limited to 'api/src') diff --git a/api/src/routes/store/applications.ts b/api/src/routes/store/applications.ts new file mode 100644 index 00000000..69cd716d --- /dev/null +++ b/api/src/routes/store/applications.ts @@ -0,0 +1,11 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/applications/:id", async (req: Request, res: Response) => { + //TODO + const { id } = req.params; + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/store/skus.ts b/api/src/routes/store/skus.ts new file mode 100644 index 00000000..5c37850d --- /dev/null +++ b/api/src/routes/store/skus.ts @@ -0,0 +1,11 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/skus/:id", async (req: Request, res: Response) => { + //TODO + const { id } = req.params; + res.json([]).status(200); +}); + +export default router; \ No newline at end of file -- cgit 1.5.1 From 8f68c4abcf8cb1e15a694795a3a13538bc4f1dcc Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Tue, 14 Sep 2021 09:38:21 +0200 Subject: Fix invites creation --- api/assets/schemas.json | 5 ++++- api/src/routes/channels/#channel_id/invites.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 3f760c35..9c34f968 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -713,7 +713,10 @@ "type": "object", "properties": { "target_user_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "target_type": { "type": [ diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 2edb4fc2..71612e31 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -8,7 +8,7 @@ import { isTextChannel } from "./messages"; const router: Router = Router(); export interface InviteCreateSchema { - target_user_id?: string; + target_user_id?: string | null; target_type?: string | null; validate?: string | null; // ? what is this max_age?: number; -- cgit 1.5.1 From b17f3c053d0449823892aa08172fd6fe4d465e78 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Tue, 14 Sep 2021 09:38:52 +0200 Subject: Emit INVITE_DELETE --- api/src/routes/invites/index.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index 6e77a853..ae8a5944 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,7 +1,8 @@ import { Router, Request, Response } from "express"; -import { getPermission, Guild, Invite, Member, PublicInviteRelation } from "@fosscord/util"; +import { emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, Member, PublicInviteRelation } from "@fosscord/util"; import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; + const router: Router = Router(); router.get("/:code", route({}), async (req: Request, res: Response) => { @@ -35,7 +36,19 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => { if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); - await Promise.all([Invite.delete({ code }), Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined })]); + await Promise.all([ + Invite.delete({ code }), + Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined }), + emitEvent({ + event: "INVITE_DELETE", + guild_id: guild_id, + data: { + channel_id: channel_id, + guild_id: guild_id, + code: code + } + } as InviteDeleteEvent) + ]); res.json({ invite: invite }); }); -- cgit 1.5.1 From 8893fd16d90ad599538ccb62f8f77711aa891bbd Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 14 Sep 2021 21:16:04 +0200 Subject: fix #128 --- api/src/routes/channels/#channel_id/webhooks.ts | 61 +++++++++++++++++++++---- 1 file changed, 52 insertions(+), 9 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 7b894455..9c8df3fb 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,9 +1,21 @@ import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; +import { handleFile, route } from "@fosscord/api"; +import { + Channel, + Config, + emitEvent, + getPermission, + Snowflake, + trimSpecial, + User, + Webhook, + WebhooksUpdateEvent, + WebhookType +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; import { DiscordApiErrors } from "@fosscord/util"; +import { generateToken } from "../../auth/login"; const router: Router = Router(); // TODO: webhooks @@ -11,13 +23,26 @@ export interface WebhookCreateSchema { /** * @maxLength 80 */ - name: string; - avatar: string; + name?: string; + avatar?: string; } +router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req, res) => { + const webhooks = await Webhook.find({ + where: { channel_id: req.params.channel_id }, + select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], + relations: ["user", "application", "source_guild"] + }); + + res.json(webhooks); +}); + // TODO: use Image Data Type for avatar instead of String router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; + var { avatar, name } = req.body as WebhookCreateSchema; + name = trimSpecial(name) || "Webhook"; + if (name === "clyde") throw new HTTPError("Invalid name", 400); + const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); isTextChannel(channel.type); @@ -27,11 +52,29 @@ router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOO const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - var { avatar, name } = req.body as { name: string; avatar?: string }; - name = trimSpecial(name); - if (name === "clyde") throw new HTTPError("Invalid name", 400); - + const id = Snowflake.generate(); // TODO: save webhook in database and send response + const webhook = await new Webhook({ + id, + name, + avatar: await handleFile(`/icons/${id}`, avatar), + user: await User.getPublicUser(req.user_id), + guild_id: channel.guild_id, + channel_id, + token: await generateToken(id), + type: WebhookType.Incoming + }).save(); + + await emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: channel.guild_id + } + } as WebhooksUpdateEvent); + + return res.json(webhook); }); export default router; -- cgit 1.5.1 From 8f862f0e5dba3985b4f38406fc19b5c5350324b9 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 14 Sep 2021 21:16:15 +0200 Subject: fix #129 --- api/src/routes/guilds/#guild_id/webhooks.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 api/src/routes/guilds/#guild_id/webhooks.ts (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/webhooks.ts b/api/src/routes/guilds/#guild_id/webhooks.ts new file mode 100644 index 00000000..a9dd164a --- /dev/null +++ b/api/src/routes/guilds/#guild_id/webhooks.ts @@ -0,0 +1,17 @@ +import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; +import { Webhook } from "@fosscord/util"; + +const router: Router = Router(); + +router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { + const webhooks = await Webhook.find({ + where: { guild_id: req.params.guild_id }, + select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], + relations: ["user", "application", "source_guild"] + }); + + return res.json(webhooks); +}); + +export default router; -- cgit 1.5.1 From df2b83ac158be1e7233d8edce59033c15c193599 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:15:55 +0200 Subject: :construction: webhook --- api/assets/schemas.json | 255 +++++++++++++++++++++++- api/client_test/index.html | 2 +- api/src/middlewares/Authentication.ts | 4 +- api/src/middlewares/ErrorHandler.ts | 7 +- api/src/routes/discoverable-guilds.ts | 2 +- api/src/routes/guilds/#guild_id/integrations.ts | 10 + api/src/routes/template.ts.disabled | 2 +- api/src/routes/webhooks/#webhook_id/index.ts | 89 +++++++++ api/src/util/route.ts | 8 +- util/src/entities/Webhook.ts | 6 +- util/src/util/Regex.ts | 2 +- 11 files changed, 369 insertions(+), 18 deletions(-) create mode 100644 api/src/routes/guilds/#guild_id/integrations.ts create mode 100644 api/src/routes/webhooks/#webhook_id/index.ts (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 9c34f968..88558cfa 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1770,10 +1770,6 @@ } }, "additionalProperties": false, - "required": [ - "avatar", - "name" - ], "definitions": { "ChannelType": { "enum": [ @@ -7446,5 +7442,256 @@ } }, "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WebhookModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" } } \ No newline at end of file diff --git a/api/client_test/index.html b/api/client_test/index.html index ac66df06..335b477c 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -11,7 +11,7 @@ window.__OVERLAY__ = /overlay/.test(location.pathname); window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname); window.GLOBAL_ENV = { - API_ENDPOINT: "/api", + API_ENDPOINT: `//${location.host}/api`, API_VERSION: 9, GATEWAY_ENDPOINT: `${location.protocol === "https:" ? "wss://" : "ws://"}${location.hostname}:3002`, WEBAPP_ENDPOINT: "", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index a300c786..32307f42 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -5,11 +5,11 @@ import { checkToken, Config } from "@fosscord/util"; export const NO_AUTHORIZATION_ROUTES = [ "/auth/login", "/auth/register", - "/webhooks/", "/ping", "/gateway", "/experiments", - /\/guilds\/\d+\/widget\.(json|png)/ + /\/guilds\/\d+\/widget\.(json|png)/, + /\/webhooks\/\d+\/\w+/ // only exclude webhook calls with webhook token ]; export const API_PREFIX = /^\/api(\/v\d+)?/; diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index d288f3fb..338da8d5 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,9 +1,10 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { EntityNotFoundError } from "typeorm"; import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; +const EntityNotFoundErrorRegex = /"(\w+)"/; + export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) return next(); @@ -18,8 +19,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne code = error.code; message = error.message; httpcode = error.httpStatus; - } else if (error instanceof EntityNotFoundError) { - message = `${(error as any).stringifyTarget || "Item"} could not be found`; + } else if (error.name === "EntityNotFoundError") { + message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; code = 404; } else if (error instanceof FieldError) { code = Number(error.code); diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts index f667eb2a..71789123 100644 --- a/api/src/routes/discoverable-guilds.ts +++ b/api/src/routes/discoverable-guilds.ts @@ -10,7 +10,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { // ! this only works using SQL querys // TODO: implement this with default typeorm query // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) }); - const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit)) }); + const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit) || 50) }); res.send({ guilds: guilds }); }); diff --git a/api/src/routes/guilds/#guild_id/integrations.ts b/api/src/routes/guilds/#guild_id/integrations.ts new file mode 100644 index 00000000..f6b8e99d --- /dev/null +++ b/api/src/routes/guilds/#guild_id/integrations.ts @@ -0,0 +1,10 @@ +import { route } from "@fosscord/api"; +import { Router, Request, Response } from "express"; +const router = Router(); + +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + // TODO: integrations (followed channels, youtube, twitch) + res.send([]); +}); + +export default router; diff --git a/api/src/routes/template.ts.disabled b/api/src/routes/template.ts.disabled index ad785f10..524e981b 100644 --- a/api/src/routes/template.ts.disabled +++ b/api/src/routes/template.ts.disabled @@ -4,7 +4,7 @@ import { Router, Request, Response } from "express"; const router = Router(); router.get("/", async (req: Request, res: Response) => { - res.send({}); + res.json({}); }); export default router; diff --git a/api/src/routes/webhooks/#webhook_id/index.ts b/api/src/routes/webhooks/#webhook_id/index.ts new file mode 100644 index 00000000..e9b40ebf --- /dev/null +++ b/api/src/routes/webhooks/#webhook_id/index.ts @@ -0,0 +1,89 @@ +import { Channel, Config, emitEvent, JWTOptions, Webhook, WebhooksUpdateEvent } from "@fosscord/util"; +import { route, Authentication, handleFile } from "@fosscord/api"; +import { Router, Request, Response, NextFunction } from "express"; +import jwt from "jsonwebtoken"; +import { HTTPError } from "lambert-server"; +const router = Router(); + +export interface WebhookModifySchema { + name?: string; + avatar?: string; + // channel_id?: string; // TODO +} + +function validateWebhookToken(req: Request, res: Response, next: NextFunction) { + const { jwtSecret } = Config.get().security; + + jwt.verify(req.params.token, jwtSecret, JWTOptions, async (err, decoded: any) => { + if (err) return next(new HTTPError("Invalid Token", 401)); + next(); + }); +} + +router.get("/", route({}), async (req: Request, res: Response) => { + res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); +}); + +router.get("/:token", route({}), validateWebhookToken, async (req: Request, res: Response) => { + res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); +}); + +router.patch("/", route({ body: "WebhookModifySchema", permission: "MANAGE_WEBHOOKS" }), (req: Request, res: Response) => { + return updateWebhook(req, res); +}); + +router.patch("/:token", route({ body: "WebhookModifySchema" }), validateWebhookToken, (req: Request, res: Response) => { + return updateWebhook(req, res); +}); + +async function updateWebhook(req: Request, res: Response) { + const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); + if (req.body.channel_id) await Channel.findOneOrFail({ id: req.body.channel_id, guild_id: webhook.guild_id }); + + webhook.assign({ + ...req.body, + avatar: await handleFile(`/icons/${req.params.webhook_id}`, req.body.avatar) + }); + + await Promise.all([ + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id: webhook.channel_id, + data: { + channel_id: webhook.channel_id, + guild_id: webhook.guild_id + } + } as WebhooksUpdateEvent), + webhook.save() + ]); + + res.json(webhook); +} + +router.delete("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { + return deleteWebhook(req, res); +}); + +router.delete("/:token", route({}), validateWebhookToken, (req: Request, res: Response) => { + return deleteWebhook(req, res); +}); + +async function deleteWebhook(req: Request, res: Response) { + const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); + + await Promise.all([ + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id: webhook.channel_id, + data: { + channel_id: webhook.channel_id, + guild_id: webhook.guild_id + } + } as WebhooksUpdateEvent), + webhook.remove() + ]); + + res.sendStatus(204); +} + +export default router; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 6cd8f622..1e2beb5d 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions, Webhook } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -54,9 +54,13 @@ export function route(opts: RouteOptions) { return async (req: Request, res: Response, next: NextFunction) => { if (opts.permission) { const required = new Permissions(opts.permission); + if (req.params.webhook_id) { + const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); + req.params.channel_id = webhook.channel_id; + req.params.guild_id = webhook.guild_id; + } const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); - // bitfield comparison: check if user lacks certain permission if (!permission.has(required)) { throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); } diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts index 12ba0d08..d0d98804 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts @@ -18,13 +18,13 @@ export class Webhook extends BaseClass { @Column({ type: "simple-enum", enum: WebhookType }) type: WebhookType; - @Column({ nullable: true }) - name?: string; + @Column() + name: string; @Column({ nullable: true }) avatar?: string; - @Column({ nullable: true }) + @Column({ nullable: true, select: false }) token?: string; @Column({ nullable: true }) diff --git a/util/src/util/Regex.ts b/util/src/util/Regex.ts index 83fc9fe8..b5d23b7f 100644 --- a/util/src/util/Regex.ts +++ b/util/src/util/Regex.ts @@ -1,5 +1,5 @@ export const DOUBLE_WHITE_SPACE = /\s\s+/g; -export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; +export const SPECIAL_CHAR = /[@#\r\n\t\f\v]/gu; export const CHANNEL_MENTION = /<#(\d+)>/g; export const USER_MENTION = /<@!?(\d+)>/g; export const ROLE_MENTION = /<@&(\d+)>/g; -- cgit 1.5.1 From d2d7dd0561e9ccfbf68caccafffa114a45b29fc0 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:49:07 +0200 Subject: Revert ":construction: webhook" This reverts commit df2b83ac158be1e7233d8edce59033c15c193599. --- api/assets/schemas.json | 255 +----------------------- api/client_test/index.html | 2 +- api/src/middlewares/Authentication.ts | 4 +- api/src/middlewares/ErrorHandler.ts | 7 +- api/src/routes/discoverable-guilds.ts | 2 +- api/src/routes/guilds/#guild_id/integrations.ts | 10 - api/src/routes/template.ts.disabled | 2 +- api/src/routes/webhooks/#webhook_id/index.ts | 89 --------- api/src/util/route.ts | 8 +- util/src/entities/Webhook.ts | 6 +- util/src/util/Regex.ts | 2 +- 11 files changed, 18 insertions(+), 369 deletions(-) delete mode 100644 api/src/routes/guilds/#guild_id/integrations.ts delete mode 100644 api/src/routes/webhooks/#webhook_id/index.ts (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 88558cfa..9c34f968 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1770,6 +1770,10 @@ } }, "additionalProperties": false, + "required": [ + "avatar", + "name" + ], "definitions": { "ChannelType": { "enum": [ @@ -7442,256 +7446,5 @@ } }, "$schema": "http://json-schema.org/draft-07/schema#" - }, - "WebhookModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "avatar": { - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, - "ChannelPermissionOverwriteType": { - "enum": [ - 0, - 1 - ], - "type": "number" - }, - "Embed": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "type": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, - "description": { - "type": "string" - }, - "url": { - "type": "string" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "color": { - "type": "integer" - }, - "footer": { - "type": "object", - "properties": { - "text": { - "type": "string" - }, - "icon_url": { - "type": "string" - }, - "proxy_icon_url": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "text" - ] - }, - "image": { - "$ref": "#/definitions/EmbedImage" - }, - "thumbnail": { - "$ref": "#/definitions/EmbedImage" - }, - "video": { - "$ref": "#/definitions/EmbedImage" - }, - "provider": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "author": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string" - }, - "icon_url": { - "type": "string" - }, - "proxy_icon_url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "fields": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - }, - "inline": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "name", - "value" - ] - } - } - }, - "additionalProperties": false - }, - "EmbedImage": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "proxy_url": { - "type": "string" - }, - "height": { - "type": "integer" - }, - "width": { - "type": "integer" - } - }, - "additionalProperties": false - }, - "ChannelModifySchema": { - "type": "object", - "properties": { - "name": { - "maxLength": 100, - "type": "string" - }, - "type": { - "$ref": "#/definitions/ChannelType" - }, - "topic": { - "type": "string" - }, - "bitrate": { - "type": "integer" - }, - "user_limit": { - "type": "integer" - }, - "rate_limit_per_user": { - "type": "integer" - }, - "position": { - "type": "integer" - }, - "permission_overwrites": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/definitions/ChannelPermissionOverwriteType" - }, - "allow": { - "type": "bigint" - }, - "deny": { - "type": "bigint" - } - }, - "additionalProperties": false, - "required": [ - "allow", - "deny", - "id", - "type" - ] - } - }, - "parent_id": { - "type": "string" - }, - "id": { - "type": "string" - }, - "nsfw": { - "type": "boolean" - }, - "rtc_region": { - "type": "string" - }, - "default_auto_archive_duration": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" } } \ No newline at end of file diff --git a/api/client_test/index.html b/api/client_test/index.html index 335b477c..ac66df06 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -11,7 +11,7 @@ window.__OVERLAY__ = /overlay/.test(location.pathname); window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname); window.GLOBAL_ENV = { - API_ENDPOINT: `//${location.host}/api`, + API_ENDPOINT: "/api", API_VERSION: 9, GATEWAY_ENDPOINT: `${location.protocol === "https:" ? "wss://" : "ws://"}${location.hostname}:3002`, WEBAPP_ENDPOINT: "", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index 32307f42..a300c786 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -5,11 +5,11 @@ import { checkToken, Config } from "@fosscord/util"; export const NO_AUTHORIZATION_ROUTES = [ "/auth/login", "/auth/register", + "/webhooks/", "/ping", "/gateway", "/experiments", - /\/guilds\/\d+\/widget\.(json|png)/, - /\/webhooks\/\d+\/\w+/ // only exclude webhook calls with webhook token + /\/guilds\/\d+\/widget\.(json|png)/ ]; export const API_PREFIX = /^\/api(\/v\d+)?/; diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 338da8d5..d288f3fb 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,10 +1,9 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; +import { EntityNotFoundError } from "typeorm"; import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; -const EntityNotFoundErrorRegex = /"(\w+)"/; - export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) return next(); @@ -19,8 +18,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne code = error.code; message = error.message; httpcode = error.httpStatus; - } else if (error.name === "EntityNotFoundError") { - message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; + } else if (error instanceof EntityNotFoundError) { + message = `${(error as any).stringifyTarget || "Item"} could not be found`; code = 404; } else if (error instanceof FieldError) { code = Number(error.code); diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts index 71789123..f667eb2a 100644 --- a/api/src/routes/discoverable-guilds.ts +++ b/api/src/routes/discoverable-guilds.ts @@ -10,7 +10,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { // ! this only works using SQL querys // TODO: implement this with default typeorm query // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) }); - const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit) || 50) }); + const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit)) }); res.send({ guilds: guilds }); }); diff --git a/api/src/routes/guilds/#guild_id/integrations.ts b/api/src/routes/guilds/#guild_id/integrations.ts deleted file mode 100644 index f6b8e99d..00000000 --- a/api/src/routes/guilds/#guild_id/integrations.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { route } from "@fosscord/api"; -import { Router, Request, Response } from "express"; -const router = Router(); - -router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - // TODO: integrations (followed channels, youtube, twitch) - res.send([]); -}); - -export default router; diff --git a/api/src/routes/template.ts.disabled b/api/src/routes/template.ts.disabled index 524e981b..ad785f10 100644 --- a/api/src/routes/template.ts.disabled +++ b/api/src/routes/template.ts.disabled @@ -4,7 +4,7 @@ import { Router, Request, Response } from "express"; const router = Router(); router.get("/", async (req: Request, res: Response) => { - res.json({}); + res.send({}); }); export default router; diff --git a/api/src/routes/webhooks/#webhook_id/index.ts b/api/src/routes/webhooks/#webhook_id/index.ts deleted file mode 100644 index e9b40ebf..00000000 --- a/api/src/routes/webhooks/#webhook_id/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Channel, Config, emitEvent, JWTOptions, Webhook, WebhooksUpdateEvent } from "@fosscord/util"; -import { route, Authentication, handleFile } from "@fosscord/api"; -import { Router, Request, Response, NextFunction } from "express"; -import jwt from "jsonwebtoken"; -import { HTTPError } from "lambert-server"; -const router = Router(); - -export interface WebhookModifySchema { - name?: string; - avatar?: string; - // channel_id?: string; // TODO -} - -function validateWebhookToken(req: Request, res: Response, next: NextFunction) { - const { jwtSecret } = Config.get().security; - - jwt.verify(req.params.token, jwtSecret, JWTOptions, async (err, decoded: any) => { - if (err) return next(new HTTPError("Invalid Token", 401)); - next(); - }); -} - -router.get("/", route({}), async (req: Request, res: Response) => { - res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); -}); - -router.get("/:token", route({}), validateWebhookToken, async (req: Request, res: Response) => { - res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); -}); - -router.patch("/", route({ body: "WebhookModifySchema", permission: "MANAGE_WEBHOOKS" }), (req: Request, res: Response) => { - return updateWebhook(req, res); -}); - -router.patch("/:token", route({ body: "WebhookModifySchema" }), validateWebhookToken, (req: Request, res: Response) => { - return updateWebhook(req, res); -}); - -async function updateWebhook(req: Request, res: Response) { - const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); - if (req.body.channel_id) await Channel.findOneOrFail({ id: req.body.channel_id, guild_id: webhook.guild_id }); - - webhook.assign({ - ...req.body, - avatar: await handleFile(`/icons/${req.params.webhook_id}`, req.body.avatar) - }); - - await Promise.all([ - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id: webhook.channel_id, - data: { - channel_id: webhook.channel_id, - guild_id: webhook.guild_id - } - } as WebhooksUpdateEvent), - webhook.save() - ]); - - res.json(webhook); -} - -router.delete("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - return deleteWebhook(req, res); -}); - -router.delete("/:token", route({}), validateWebhookToken, (req: Request, res: Response) => { - return deleteWebhook(req, res); -}); - -async function deleteWebhook(req: Request, res: Response) { - const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); - - await Promise.all([ - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id: webhook.channel_id, - data: { - channel_id: webhook.channel_id, - guild_id: webhook.guild_id - } - } as WebhooksUpdateEvent), - webhook.remove() - ]); - - res.sendStatus(204); -} - -export default router; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 1e2beb5d..6cd8f622 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions, Webhook } from "@fosscord/util"; +import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -54,13 +54,9 @@ export function route(opts: RouteOptions) { return async (req: Request, res: Response, next: NextFunction) => { if (opts.permission) { const required = new Permissions(opts.permission); - if (req.params.webhook_id) { - const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); - req.params.channel_id = webhook.channel_id; - req.params.guild_id = webhook.guild_id; - } const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); + // bitfield comparison: check if user lacks certain permission if (!permission.has(required)) { throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); } diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts index d0d98804..12ba0d08 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts @@ -18,13 +18,13 @@ export class Webhook extends BaseClass { @Column({ type: "simple-enum", enum: WebhookType }) type: WebhookType; - @Column() - name: string; + @Column({ nullable: true }) + name?: string; @Column({ nullable: true }) avatar?: string; - @Column({ nullable: true, select: false }) + @Column({ nullable: true }) token?: string; @Column({ nullable: true }) diff --git a/util/src/util/Regex.ts b/util/src/util/Regex.ts index b5d23b7f..83fc9fe8 100644 --- a/util/src/util/Regex.ts +++ b/util/src/util/Regex.ts @@ -1,5 +1,5 @@ export const DOUBLE_WHITE_SPACE = /\s\s+/g; -export const SPECIAL_CHAR = /[@#\r\n\t\f\v]/gu; +export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; export const CHANNEL_MENTION = /<#(\d+)>/g; export const USER_MENTION = /<@!?(\d+)>/g; export const ROLE_MENTION = /<@&(\d+)>/g; -- cgit 1.5.1 From 173f03c596c2df23b81d3c7601988f0e090e6e3a Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:49:12 +0200 Subject: Revert "fix #129" This reverts commit 8f862f0e5dba3985b4f38406fc19b5c5350324b9. --- api/src/routes/guilds/#guild_id/webhooks.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 api/src/routes/guilds/#guild_id/webhooks.ts (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/webhooks.ts b/api/src/routes/guilds/#guild_id/webhooks.ts deleted file mode 100644 index a9dd164a..00000000 --- a/api/src/routes/guilds/#guild_id/webhooks.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -import { Webhook } from "@fosscord/util"; - -const router: Router = Router(); - -router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - const webhooks = await Webhook.find({ - where: { guild_id: req.params.guild_id }, - select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], - relations: ["user", "application", "source_guild"] - }); - - return res.json(webhooks); -}); - -export default router; -- cgit 1.5.1 From 60ee23489125cd48903501fd03e410affde6b7b2 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:49:17 +0200 Subject: Revert "fix #128" This reverts commit 8893fd16d90ad599538ccb62f8f77711aa891bbd. --- api/src/routes/channels/#channel_id/webhooks.ts | 61 ++++--------------------- 1 file changed, 9 insertions(+), 52 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 9c8df3fb..7b894455 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,21 +1,9 @@ import { Router, Response, Request } from "express"; -import { handleFile, route } from "@fosscord/api"; -import { - Channel, - Config, - emitEvent, - getPermission, - Snowflake, - trimSpecial, - User, - Webhook, - WebhooksUpdateEvent, - WebhookType -} from "@fosscord/util"; +import { route } from "@fosscord/api"; +import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; import { DiscordApiErrors } from "@fosscord/util"; -import { generateToken } from "../../auth/login"; const router: Router = Router(); // TODO: webhooks @@ -23,26 +11,13 @@ export interface WebhookCreateSchema { /** * @maxLength 80 */ - name?: string; - avatar?: string; + name: string; + avatar: string; } -router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req, res) => { - const webhooks = await Webhook.find({ - where: { channel_id: req.params.channel_id }, - select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], - relations: ["user", "application", "source_guild"] - }); - - res.json(webhooks); -}); - // TODO: use Image Data Type for avatar instead of String router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - var { avatar, name } = req.body as WebhookCreateSchema; - name = trimSpecial(name) || "Webhook"; - if (name === "clyde") throw new HTTPError("Invalid name", 400); - const { channel_id } = req.params; + const channel_id = req.params.channel_id; const channel = await Channel.findOneOrFail({ id: channel_id }); isTextChannel(channel.type); @@ -52,29 +27,11 @@ router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOO const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - const id = Snowflake.generate(); - // TODO: save webhook in database and send response - const webhook = await new Webhook({ - id, - name, - avatar: await handleFile(`/icons/${id}`, avatar), - user: await User.getPublicUser(req.user_id), - guild_id: channel.guild_id, - channel_id, - token: await generateToken(id), - type: WebhookType.Incoming - }).save(); - - await emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: channel.guild_id - } - } as WebhooksUpdateEvent); + var { avatar, name } = req.body as { name: string; avatar?: string }; + name = trimSpecial(name); + if (name === "clyde") throw new HTTPError("Invalid name", 400); - return res.json(webhook); + // TODO: save webhook in database and send response }); export default router; -- cgit 1.5.1 From 0506d40aa13ade16b627808f3ad340cee2a8e888 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 16 Sep 2021 21:31:39 +0200 Subject: Dummy sticker-packs routes --- api/src/routes/sticker-packs/#id/index.ts | 18 ++++++++++++++++++ api/src/routes/sticker-packs/index.ts | 10 ++++++++++ 2 files changed, 28 insertions(+) create mode 100644 api/src/routes/sticker-packs/#id/index.ts create mode 100644 api/src/routes/sticker-packs/index.ts (limited to 'api/src') diff --git a/api/src/routes/sticker-packs/#id/index.ts b/api/src/routes/sticker-packs/#id/index.ts new file mode 100644 index 00000000..2344a48f --- /dev/null +++ b/api/src/routes/sticker-packs/#id/index.ts @@ -0,0 +1,18 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json({ + id: "", + stickers: [], + name: "", + sku_id: "", + cover_sticker_id: "", + description: "", + banner_asset_id: "" + }).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/sticker-packs/index.ts b/api/src/routes/sticker-packs/index.ts new file mode 100644 index 00000000..6c4e46d8 --- /dev/null +++ b/api/src/routes/sticker-packs/index.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json({ sticker_packs: [] }).status(200); +}); + +export default router; \ No newline at end of file -- cgit 1.5.1 From d630f09f80b772c3943058405a6ef0edc48b4cba Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 16 Sep 2021 21:33:36 +0200 Subject: Implemented DMs and group DMs --- api/src/routes/channels/#channel_id/index.ts | 28 +++++-- .../#channel_id/messages/#message_id/ack.ts | 2 +- .../routes/channels/#channel_id/messages/index.ts | 46 ++++++++--- api/src/routes/channels/#channel_id/recipients.ts | 56 +++++++++++++- api/src/routes/users/#id/profile.ts | 1 + api/src/routes/users/@me/channels.ts | 43 ++++------- api/src/routes/users/@me/relationships.ts | 19 ++++- gateway/src/opcodes/Identify.ts | 38 +++++----- gateway/src/opcodes/index.ts | 1 + util/src/dtos/DmChannelDTO.ts | 35 +++++++++ util/src/dtos/UserDTO.ts | 17 +++++ util/src/dtos/index.ts | 2 + util/src/entities/Channel.ts | 15 ++-- util/src/entities/Recipient.ts | 3 + util/src/entities/User.ts | 2 +- util/src/index.ts | 2 + util/src/interfaces/Event.ts | 22 ++++++ util/src/services/ChannelService.ts | 88 ++++++++++++++++++++++ util/src/services/index.ts | 1 + 19 files changed, 342 insertions(+), 79 deletions(-) create mode 100644 util/src/dtos/DmChannelDTO.ts create mode 100644 util/src/dtos/UserDTO.ts create mode 100644 util/src/dtos/index.ts create mode 100644 util/src/services/ChannelService.ts create mode 100644 util/src/services/index.ts (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 02ac9884..e836622b 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,6 +1,7 @@ -import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util"; -import { Router, Response, Request } from "express"; +import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; + const router: Router = Router(); // TODO: delete channel // TODO: Get channel @@ -16,14 +17,27 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); - // TODO: Dm channel "close" not delete - const data = channel; + if (channel.type === ChannelType.DM) { + const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }) + recipient.closed = true + await Promise.all([ + recipient.save(), + emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent) + ]); - await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]); + } else if (channel.type === ChannelType.GROUP_DM) { + await ChannelService.removeRecipientFromChannel(channel, req.user_id) + } else { + //TODO messages in this channel should be deleted before deleting the channel + await Promise.all([ + Channel.delete({ id: channel_id }), + emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) + ]); + } - res.send(data); + res.send(channel); }); export interface ChannelModifySchema { diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index 97d1d19e..786e4581 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -26,7 +26,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques data: { channel_id, message_id, - version: 496 + version: 3763 } } as MessageAckEvent); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index ec93649e..bb610a6a 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,9 +1,8 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; +import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import multer from "multer"; -import { sendMessage } from "@fosscord/api"; import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; @@ -62,9 +61,9 @@ router.get("/", async (req: Request, res: Response) => { if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); - const around = `${req.query.around}`; - const before = `${req.query.before}`; - const after = `${req.query.after}`; + const around = req.query.around ? `${req.query.around}` : undefined; + const before = req.query.before ? `${req.query.before}` : undefined; + const after = req.query.after ? `${req.query.after}` : undefined; const limit = Number(req.query.limit) || 50; if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100"); @@ -151,10 +150,12 @@ router.post( return res.status(400).json(error); } } + //TODO querying the DB at every message post should be avoided, caching maybe? + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }) const embeds = []; if (body.embed) embeds.push(body.embed); - const data = await sendMessage({ + let message = await handleMessage({ ...body, type: 0, pinned: false, @@ -162,9 +163,36 @@ router.post( embeds, channel_id, attachments, - edited_timestamp: undefined + edited_timestamp: undefined, + timestamp: new Date() }); - return res.json(data); + message = await message.save() + + await channel.assign({ last_message_id: message.id }).save() + + if (channel.isDm()) { + const channel_dto = await DmChannelDTO.from(channel) + + for (let recipient of channel.recipients!) { + if (recipient.closed) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }) + } + } + + await Promise.all(channel.recipients!.map(async r => { + r.closed = false; + return await r.save() + })); + } + + await emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent) + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error + + return res.json(message); } ); diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index ea6bc563..d88b38f3 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -1,5 +1,57 @@ -import { Router, Response, Request } from "express"; +import { Request, Response, Router } from "express"; +import { Channel, ChannelRecipientAddEvent, ChannelService, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; + const router: Router = Router(); -// TODO: + +router.put("/:user_id", async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); + + if (channel.type !== ChannelType.GROUP_DM) { + const recipients = [ + ...channel.recipients!.map(r => r.user_id), + user_id + ].unique() + + const new_channel = await ChannelService.createDMChannel(recipients, req.user_id) + return res.status(201).json(new_channel); + } else { + if (channel.recipients!.map(r => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error? + } + + channel.recipients!.push(new Recipient({ channel_id: channel_id, user_id: user_id })); + await channel.save() + + await emitEvent({ + event: "CHANNEL_CREATE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + await emitEvent({ + event: "CHANNEL_RECIPIENT_ADD", data: { + channel_id: channel_id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, channel_id: channel_id + } as ChannelRecipientAddEvent); + return res.sendStatus(204); + } +}); + +router.delete("/:user_id", async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); + if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) + throw DiscordApiErrors.MISSING_PERMISSIONS + + if (!channel.recipients!.map(r => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error? + } + + await ChannelService.removeRecipientFromChannel(channel, user_id) + + return res.sendStatus(204); +}); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index d60c4f86..06d5c38c 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -19,6 +19,7 @@ router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req connected_accounts: user.connected_accounts, premium_guild_since: null, // TODO premium_since: null, // TODO + mutual_guilds: [], // TODO {id: "", nick: null} when ?with_mutual_guilds=true user: { username: user.username, discriminator: user.discriminator, diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index da33f204..bd7af18c 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,15 +1,21 @@ -import { Router, Request, Response } from "express"; -import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; +import { Request, Response, Router } from "express"; +import { PublicUserProjection, Recipient, User, ChannelService } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { In } from "typeorm"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] }); + const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel", "user"] }); - res.json(recipients.map((x) => x.channel)); + //TODO check if this is right + const aa = await Promise.all(recipients.map(async (x) => { + return { + ...(x.channel), + recipients: await User.findOneOrFail({ where: { id: x.user_id }, select: PublicUserProjection }), + } + })) + + res.json(aa); }); export interface DmChannelCreateSchema { @@ -19,30 +25,7 @@ export interface DmChannelCreateSchema { router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; - - body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - - const recipients = await User.find({ where: body.recipients.map((x) => ({ id: x })) }); - - if (recipients.length !== body.recipients.length) { - throw new HTTPError("Recipient/s not found"); - } - - const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; - const name = trimSpecial(body.name); - - const channel = await new Channel({ - name, - type, - // owner_id only for group dm channels - created_at: new Date(), - last_message_id: null, - recipients: [...body.recipients.map((x) => new Recipient({ user_id: x })), new Recipient({ user_id: req.user_id })] - }).save(); - - await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); - - res.json(channel); + res.json(await ChannelService.createDMChannel(body.recipients, req.user_id, body.name)); }); export default router; diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 58d2e481..1d72f11a 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -18,9 +18,19 @@ const router = Router(); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get("/", route({}), async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] }); + const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] }); + + //TODO DTO + const related_users = user.relationships.map(r => { + return { + id: r.to.id, + type: r.type, + nickname: null, + user: r.to.toPublicUser(), + } + }) - return res.json(user.relationships); + return res.json(related_users); }); export interface RelationshipPutSchema { @@ -48,7 +58,10 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, await User.findOneOrFail({ relations: ["relationships", "relationships.to"], select: userProjection, - where: req.body as { discriminator: string; username: string } + where: { + discriminator: String(req.body.discriminator,).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes + username: req.body.username + } }), req.body.type ); diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index f6a4478f..d91cd7f2 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -88,20 +88,17 @@ export async function onIdentify(this: WebSocket, data: Payload) { const user_guild_settings_entries = members.map((x) => x.settings); const recipients = await Recipient.find({ - where: { user_id: this.user_id }, + where: { user_id: this.user_id, closed: false }, relations: ["channel", "channel.recipients", "channel.recipients.user"], // TODO: public user selection }); const channels = recipients.map((x) => { // @ts-ignore x.channel.recipients = x.channel.recipients?.map((x) => x.user); - // @ts-ignore - users = users.concat(x.channel.recipients); - if (x.channel.type === ChannelType.DM) { - x.channel.recipients = [ - // @ts-ignore - x.channel.recipients.find((x) => x.id !== this.user_id), - ]; + //TODO is this needed? check if users in group dm that are not friends are sent in the READY event + //users = users.concat(x.channel.recipients); + if (x.channel.isDm()) { + x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id); } return x.channel; }); @@ -111,16 +108,19 @@ export async function onIdentify(this: WebSocket, data: Payload) { }); if (!user) return this.close(CLOSECODES.Authentication_failed); - const public_user = { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - bot: user.bot, - bio: user.bio, - }; - users.push(public_user); + for (let relation of user.relationships) { + const related_user = relation.to + const public_related_user = { + username: related_user.username, + discriminator: related_user.discriminator, + id: related_user.id, + public_flags: related_user.public_flags, + avatar: related_user.avatar, + bot: related_user.bot, + bio: related_user.bio, + }; + users.push(public_related_user); + } const session_id = genSessionId(); this.session_id = session_id; //Set the session of the WebSocket object @@ -201,7 +201,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? - users: users.unique(), // TODO + users: users.unique(), merged_members: merged_members, // shard // TODO: only for bots sharding // application // TODO for applications diff --git a/gateway/src/opcodes/index.ts b/gateway/src/opcodes/index.ts index a6d13bfb..c4069589 100644 --- a/gateway/src/opcodes/index.ts +++ b/gateway/src/opcodes/index.ts @@ -21,5 +21,6 @@ export default { 8: onRequestGuildMembers, // 9: Invalid Session // 10: Hello + // 13: Dm_update 14: onLazyRequest, }; diff --git a/util/src/dtos/DmChannelDTO.ts b/util/src/dtos/DmChannelDTO.ts new file mode 100644 index 00000000..8b7a18fd --- /dev/null +++ b/util/src/dtos/DmChannelDTO.ts @@ -0,0 +1,35 @@ +import { MinimalPublicUserDTO } from "./UserDTO"; +import { Channel, PublicUserProjection, User } from "../entities"; + +export class DmChannelDTO { + icon: string | null; + id: string; + last_message_id: string | null; + name: string | null; + origin_channel_id: string | null; + owner_id?: string; + recipients: MinimalPublicUserDTO[]; + type: number; + + static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) { + const obj = new DmChannelDTO() + obj.icon = channel.icon || null + obj.id = channel.id + obj.last_message_id = channel.last_message_id || null + obj.name = channel.name || null + obj.origin_channel_id = origin_channel_id || null + obj.owner_id = channel.owner_id + obj.type = channel.type + obj.recipients = (await Promise.all(channel.recipients!.filter(r => !excluded_recipients.includes(r.user_id)).map(async r => { + return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection }) + }))).map(u => new MinimalPublicUserDTO(u)) + return obj + } + + excludedRecipients(excluded_recipients: string[]): DmChannelDTO { + return { + ...this, + recipients: this.recipients.filter(r => !excluded_recipients.includes(r.id)) + } + } +} \ No newline at end of file diff --git a/util/src/dtos/UserDTO.ts b/util/src/dtos/UserDTO.ts new file mode 100644 index 00000000..f09b5f4e --- /dev/null +++ b/util/src/dtos/UserDTO.ts @@ -0,0 +1,17 @@ +import { User } from "../entities"; + +export class MinimalPublicUserDTO { + avatar?: string | null; + discriminator: string; + id: string; + public_flags: number; + username: string; + + constructor(user: User) { + this.avatar = user.avatar + this.discriminator = user.discriminator + this.id = user.id + this.public_flags = user.public_flags + this.username = user.username + } +} \ No newline at end of file diff --git a/util/src/dtos/index.ts b/util/src/dtos/index.ts new file mode 100644 index 00000000..13702342 --- /dev/null +++ b/util/src/dtos/index.ts @@ -0,0 +1,2 @@ +export * from "./DmChannelDTO"; +export * from "./UserDTO"; \ No newline at end of file diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index fc954f63..6eac19ca 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,7 +1,6 @@ -import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { Message } from "./Message"; import { User } from "./User"; import { HTTPError } from "lambert-server"; import { emitEvent, getPermission, Snowflake } from "../util"; @@ -31,6 +30,9 @@ export class Channel extends BaseClass { @Column({ nullable: true }) name?: string; + @Column({ nullable: true }) + icon?: string; + @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; @@ -38,13 +40,8 @@ export class Channel extends BaseClass { recipients?: Recipient[]; @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.last_message) last_message_id: string; - @JoinColumn({ name: "last_message_id" }) - @ManyToOne(() => Message) - last_message?: Message; - @Column({ nullable: true }) @RelationId((channel: Channel) => channel.guild) guild_id?: string; @@ -162,6 +159,10 @@ export class Channel extends BaseClass { return channel; } + + isDm() { + return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM + } } export interface ChannelPermissionOverwrite { diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts index 2a27b29f..bb280588 100644 --- a/util/src/entities/Recipient.ts +++ b/util/src/entities/Recipient.ts @@ -19,5 +19,8 @@ export class Recipient extends BaseClass { @ManyToOne(() => require("./User").User) user: import("./User").User; + @Column({ default: false }) + closed: boolean; + // TODO: settings/mute/nick/added at/encryption keys/read_state } diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 736704f8..cef88777 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -124,7 +124,7 @@ export class User extends BaseClass { flags: string; // UserFlags @Column() - public_flags: string; + public_flags: number; @JoinColumn({ name: "relationship_ids" }) @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from) diff --git a/util/src/index.ts b/util/src/index.ts index f3bd9e9b..538bfdd1 100644 --- a/util/src/index.ts +++ b/util/src/index.ts @@ -4,6 +4,8 @@ import "reflect-metadata"; export * from "./util/index"; export * from "./interfaces/index"; export * from "./entities/index"; +export * from "./services/index"; +export * from "./dtos/index"; // import Config from "../util/Config"; // import db, { MongooseCache, toObject } from "./util/Database"; diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index aff50300..03099bbb 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -127,6 +127,22 @@ export interface ChannelPinsUpdateEvent extends Event { }; } +export interface ChannelRecipientAddEvent extends Event { + event: "CHANNEL_RECIPIENT_ADD"; + data: { + channel_id: string; + user: User; + }; +} + +export interface ChannelRecipientRemoveEvent extends Event { + event: "CHANNEL_RECIPIENT_REMOVE"; + data: { + channel_id: string; + user: User; + }; +} + export interface GuildCreateEvent extends Event { event: "GUILD_CREATE"; data: Guild & { @@ -436,6 +452,8 @@ export type EventData = | ChannelUpdateEvent | ChannelDeleteEvent | ChannelPinsUpdateEvent + | ChannelRecipientAddEvent + | ChannelRecipientRemoveEvent | GuildCreateEvent | GuildUpdateEvent | GuildDeleteEvent @@ -482,6 +500,8 @@ export enum EVENTEnum { ChannelUpdate = "CHANNEL_UPDATE", ChannelDelete = "CHANNEL_DELETE", ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", + ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD", + ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE", GuildCreate = "GUILD_CREATE", GuildUpdate = "GUILD_UPDATE", GuildDelete = "GUILD_DELETE", @@ -525,6 +545,8 @@ export type EVENT = | "CHANNEL_UPDATE" | "CHANNEL_DELETE" | "CHANNEL_PINS_UPDATE" + | "CHANNEL_RECIPIENT_ADD" + | "CHANNEL_RECIPIENT_REMOVE" | "GUILD_CREATE" | "GUILD_UPDATE" | "GUILD_DELETE" diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts new file mode 100644 index 00000000..7cded10f --- /dev/null +++ b/util/src/services/ChannelService.ts @@ -0,0 +1,88 @@ +import { Channel, ChannelType, PublicUserProjection, Recipient, User } from "../entities"; +import { HTTPError } from "lambert-server"; +import { emitEvent, trimSpecial } from "../util"; +import { DmChannelDTO } from "../dtos"; +import { ChannelRecipientRemoveEvent } from "../interfaces"; + +export function checker(arr: any[], target: any[]) { + return target.every(v => arr.includes(v)); +} + +export class ChannelService { + public static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { + recipients = recipients.unique().filter((x) => x !== creator_user_id); + const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + + const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; + + let channel = null; + + const channelRecipients = [...recipients, creator_user_id] + + const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] }) + + for (let ur of userRecipients) { + let re = ur.channel.recipients!.map(r => r.user_id) + if (re.length === channelRecipients.length) { + if (checker(re, channelRecipients)) { + if (channel == null) { + channel = ur.channel + await ur.assign({ closed: false }).save() + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await new Channel({ + name, + type, + owner_id: (type === ChannelType.DM ? undefined : creator_user_id), + created_at: new Date(), + last_message_id: null, + recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })), + }).save(); + } + + + const channel_dto = await DmChannelDTO.from(channel) + + if (type === ChannelType.GROUP_DM) { + + for (let recipient of channel.recipients!) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }) + } + } else { + await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); + } + + return channel_dto.excludedRecipients([creator_user_id]) + } + + public static async removeRecipientFromChannel(channel: Channel, user_id: string) { + await Recipient.delete({ channel_id: channel.id, user_id: user_id }) + + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", data: { + channel_id: channel.id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, channel_id: channel.id + } as ChannelRecipientRemoveEvent); + } +} \ No newline at end of file diff --git a/util/src/services/index.ts b/util/src/services/index.ts new file mode 100644 index 00000000..c012a208 --- /dev/null +++ b/util/src/services/index.ts @@ -0,0 +1 @@ +export * from "./ChannelService"; -- cgit 1.5.1 From 4003fef534a9b8fb2a075daa05749e3ec16cccec Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 22:03:30 +0200 Subject: :bug: fix vanity url --- api/package-lock.json | 1 + api/src/routes/guilds/#guild_id/vanity-url.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'api/src') diff --git a/api/package-lock.json b/api/package-lock.json index 79889f8a..fd9c68b4 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -68,6 +68,7 @@ } }, "../util": { + "name": "@fosscord/util", "version": "1.0.0", "hasInstallScript": true, "license": "GPLV3", diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 801768fb..7f2cea9e 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,6 +1,7 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; +import { HTTPError } from "lambert-server"; const router = Router(); @@ -29,14 +30,14 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }) const body = req.body as VanityUrlSchema; const code = body.code?.replace(InviteRegex, ""); - await Invite.findOneOrFail({ code }); + const invite = await Invite.findOne({ code }); + if (invite) throw new HTTPError("Invite already exists"); const guild = await Guild.findOneOrFail({ id: guild_id }); const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); - guild.vanity_url_code = code; Promise.all([ - guild.save(), + Guild.update({ id: guild_id }, { vanity_url_code: code }), Invite.delete({ code: guild.vanity_url_code }), new Invite({ code: code, -- cgit 1.5.1 From c4cfa42c60b7101e70bb76bdbd359daec434d78d Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Fri, 17 Sep 2021 13:59:21 +0200 Subject: Fix GET /users/@me/channels --- api/src/routes/users/@me/channels.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index bd7af18c..873ff245 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,21 +1,12 @@ import { Request, Response, Router } from "express"; -import { PublicUserProjection, Recipient, User, ChannelService } from "@fosscord/util"; +import { Recipient, ChannelService, DmChannelDTO } from "@fosscord/util"; import { route } from "@fosscord/api"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel", "user"] }); - - //TODO check if this is right - const aa = await Promise.all(recipients.map(async (x) => { - return { - ...(x.channel), - recipients: await User.findOneOrFail({ where: { id: x.user_id }, select: PublicUserProjection }), - } - })) - - res.json(aa); + const recipients = await Recipient.find({ where: { user_id: req.user_id, closed: false }, relations: ["channel", "channel.recipients"] }); + res.json(await Promise.all(recipients.map(r => DmChannelDTO.from(r.channel, [req.user_id])))); }); export interface DmChannelCreateSchema { -- cgit 1.5.1 From 50ab5e7d490413388a8365a82af43c40b252beec Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Fri, 17 Sep 2021 18:29:02 +0200 Subject: Fix icon, owner_id change and channel deletion for group DMs --- api/assets/schemas.json | 1190 +++++++++++++------------- api/src/routes/channels/#channel_id/index.ts | 8 +- cdn/src/Server.ts | 3 + util/src/entities/Channel.ts | 4 +- util/src/services/ChannelService.ts | 36 +- 5 files changed, 633 insertions(+), 608 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 9c34f968..05046b97 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -81,11 +81,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -142,27 +161,7 @@ } }, "additionalProperties": false, - "required": [ - "name", - "type" - ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -308,11 +307,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -368,11 +386,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -470,22 +484,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -631,11 +629,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -691,11 +708,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -751,22 +764,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -912,11 +909,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -972,11 +988,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1002,22 +1014,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1163,11 +1159,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1223,11 +1238,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1256,22 +1267,6 @@ "messages" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1417,11 +1412,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1477,11 +1491,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1519,22 +1529,6 @@ "type" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1680,11 +1674,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1740,11 +1753,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1775,22 +1784,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1936,11 +1929,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1996,11 +2008,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2026,22 +2034,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -2187,11 +2179,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -2247,11 +2258,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2289,22 +2296,6 @@ ] }, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -2450,11 +2441,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -2510,11 +2520,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2565,22 +2571,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -2726,11 +2716,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -2786,11 +2795,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2889,22 +2894,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3050,11 +3039,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3110,11 +3118,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3140,22 +3144,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3301,11 +3289,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3361,11 +3368,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3391,22 +3394,6 @@ "nick" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3552,11 +3539,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3612,11 +3618,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3654,22 +3656,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3815,11 +3801,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3875,11 +3880,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3912,22 +3913,6 @@ ] }, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4073,11 +4058,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4133,11 +4137,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4166,22 +4166,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4327,11 +4311,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4385,13 +4388,9 @@ }, "default_auto_archive_duration": { "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + } + }, + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4420,22 +4419,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4581,11 +4564,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4641,11 +4643,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4670,22 +4668,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4831,11 +4813,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4891,11 +4892,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4940,22 +4937,6 @@ "channel_id" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5101,11 +5082,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5161,11 +5161,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5217,22 +5213,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5378,11 +5358,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5438,11 +5437,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5472,22 +5467,6 @@ "enabled" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5633,11 +5612,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5693,11 +5691,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5726,22 +5720,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5887,11 +5865,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5947,11 +5944,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5983,22 +5976,6 @@ "recipients" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6144,11 +6121,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6204,11 +6200,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -6264,22 +6256,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6425,11 +6401,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6485,11 +6480,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -6515,22 +6506,6 @@ "type" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6676,11 +6651,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6736,11 +6730,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -6770,22 +6760,6 @@ "username" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6931,11 +6905,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6991,11 +6984,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -7208,22 +7197,6 @@ "timezone_offset" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -7369,11 +7342,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -7429,11 +7421,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index e836622b..70dd3994 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,6 +1,6 @@ import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { route } from "@fosscord/api"; +import { handleFile, route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel @@ -44,9 +44,10 @@ export interface ChannelModifySchema { /** * @maxLength 100 */ - name: string; - type: ChannelType; + name?: string; + type?: ChannelType; topic?: string; + icon?: string | null; bitrate?: number; user_limit?: number; rate_limit_per_user?: number; @@ -67,6 +68,7 @@ export interface ChannelModifySchema { router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { var payload = req.body as ChannelModifySchema; const { channel_id } = req.params; + if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); const channel = await Channel.findOneOrFail({ id: channel_id }); channel.assign(payload); diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index 5c4a8ae5..590eda6f 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -58,6 +58,9 @@ export class CDNServer extends Server { this.app.use("/team-icons/", avatarsRoute); this.log("verbose", "[Server] Route /team-icons registered"); + this.app.use("/channel-icons/", avatarsRoute); + this.log("verbose", "[Server] Route /channel-icons registered"); + return super.start(); } diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 6eac19ca..aa2bfab3 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -30,8 +30,8 @@ export class Channel extends BaseClass { @Column({ nullable: true }) name?: string; - @Column({ nullable: true }) - icon?: string; + @Column({ type: 'text', nullable: true }) + icon?: string | null; @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts index 319475b6..8f57a28a 100644 --- a/util/src/services/ChannelService.ts +++ b/util/src/services/ChannelService.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelType, PublicUserProjection, Recipient, User } from "../entities"; +import { Channel, ChannelType, Message, PublicUserProjection, Recipient, User } from "../entities"; import { HTTPError } from "lambert-server"; import { emitEvent, trimSpecial } from "../util"; import { DmChannelDTO } from "../dtos"; @@ -72,10 +72,36 @@ export class ChannelService { public static async removeRecipientFromChannel(channel: Channel, user_id: string) { await Recipient.delete({ channel_id: channel.id, user_id: user_id }) + channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id) + + if (channel.recipients?.length === 0) { + await ChannelService.deleteChannel(channel); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + return + } + + let channel_dto = null; + + //If the owner leave we make the first recipient in the list the new owner + if (channel.owner_id === user_id) { + channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? + channel_dto = await DmChannelDTO.from(channel, [user_id]) + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel_dto, + channel_id: channel.id + }); + } + + await channel.save() await emitEvent({ event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), + data: channel_dto !== null ? channel_dto : await DmChannelDTO.from(channel, [user_id]), user_id: user_id }); @@ -86,4 +112,10 @@ export class ChannelService { }, channel_id: channel.id } as ChannelRecipientRemoveEvent); } + + public static async deleteChannel(channel: Channel) { + await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util + //TODO before deleting the channel we should check and delete other relations + await Channel.delete({ id: channel.id }) + } } -- cgit 1.5.1 From 859fdd679b2f42ae4fd90b3a1a7958370fcdccb8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 17 Sep 2021 23:42:40 +0200 Subject: :bug: fix body parse treating null not as undefined (except for icons/avatars) --- api/assets/schemas.json | 63 +++++++++----------------- api/package.json | 3 +- api/scripts/globalSetup.js | 3 +- api/src/routes/auth/register.ts | 30 ++++++------ api/src/routes/channels/#channel_id/invites.ts | 6 +-- api/src/routes/guilds/#guild_id/index.ts | 6 +-- api/src/routes/guilds/index.ts | 4 +- api/src/routes/guilds/templates/index.ts | 2 +- api/src/routes/users/@me/index.ts | 2 +- api/src/util/route.ts | 45 ++++++++++++++---- 10 files changed, 88 insertions(+), 76 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 9c34f968..e27087a9 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -26,8 +26,7 @@ "type": "string" }, "date_of_birth": { - "type": "string", - "format": "date-time" + "type": "string" }, "gift_code_sku_id": { "type": "string" @@ -713,22 +712,13 @@ "type": "object", "properties": { "target_user_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "target_type": { - "type": [ - "null", - "string" - ] + "type": "string" }, "validate": { - "type": [ - "null", - "string" - ] + "type": "string" }, "max_age": { "type": "integer" @@ -2539,7 +2529,10 @@ "type": "string" }, "icon": { - "type": "string" + "type": [ + "null", + "string" + ] }, "channels": { "type": "array", @@ -2551,10 +2544,7 @@ "type": "string" }, "system_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "rules_channel_id": { "type": "string" @@ -2820,10 +2810,7 @@ ] }, "description": { - "type": [ - "null", - "string" - ] + "type": "string" }, "features": { "type": "array", @@ -2844,19 +2831,13 @@ "type": "integer" }, "public_updates_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "afk_timeout": { "type": "integer" }, "afk_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "preferred_locale": { "type": "string" @@ -2869,16 +2850,16 @@ "type": "string" }, "icon": { - "type": "string" + "type": [ + "null", + "string" + ] }, "guild_template_code": { "type": "string" }, "system_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "rules_channel_id": { "type": "string" @@ -5718,7 +5699,10 @@ "type": "string" }, "avatar": { - "type": "string" + "type": [ + "null", + "string" + ] } }, "additionalProperties": false, @@ -6241,10 +6225,7 @@ "type": "string" }, "accent_color": { - "type": [ - "null", - "integer" - ] + "type": "integer" }, "banner": { "type": [ diff --git a/api/package.json b/api/package.json index d93a6269..ad959e57 100644 --- a/api/package.json +++ b/api/package.json @@ -5,7 +5,8 @@ "main": "dist/Server.js", "types": "dist/Server.d.ts", "scripts": { - "test": "npm run build && jest --coverage --verbose --forceExit ./tests", + "test:only": "node -r ./scripts/tsconfig-paths-bootstrap.js node_modules/.bin/jest --coverage --verbose --forceExit ./tests", + "test": "npm run build && npm run test:only", "test:watch": "jest --watch", "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start", "build": "npx tsc -b .", diff --git a/api/scripts/globalSetup.js b/api/scripts/globalSetup.js index 76cd8e0d..98e70fb9 100644 --- a/api/scripts/globalSetup.js +++ b/api/scripts/globalSetup.js @@ -1,10 +1,11 @@ const fs = require("fs"); +const path = require("path"); const { FosscordServer } = require("../dist/Server"); const Server = new FosscordServer({ port: 3001 }); global.server = Server; module.exports = async () => { try { - fs.unlinkSync(`${__dirname}/../database.db`); + fs.unlinkSync(path.join(__dirname, "..", "database.db")); } catch {} return await Server.start(); }; diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 33f089b2..efe91625 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -27,13 +27,16 @@ export interface RegisterSchema { email?: string; fingerprint?: string; invite?: string; + /** + * @TJS-type string + */ date_of_birth?: Date; // "2000-04-03" gift_code_sku_id?: string; captcha_key?: string; } router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => { - const { + let { email, username, password, @@ -61,14 +64,11 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re // TODO: gift_code_sku_id? // TODO: check password strength - // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email = adjustEmail(email); - - // adjusted_password will be the hash of the password - let adjusted_password = ""; + // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick + email = adjustEmail(email); // trim special uf8 control characters -> Backspace, Newline, ... - let adjusted_username = trimSpecial(username); + username = trimSpecial(username); // discriminator will be randomly generated let discriminator = ""; @@ -96,10 +96,10 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re if (email) { // replace all dots and chars after +, if its a gmail.com email - if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); + if (!email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); // check if there is already an account with this email - const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); + const exists = await User.findOneOrFail({ email: email }).catch((e) => {}); if (exists) { throw FieldErrors({ @@ -122,6 +122,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } else if (register.dateOfBirth.minimum) { const minimum = new Date(); minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); + date_of_birth = new Date(date_of_birth); // higher is younger if (date_of_birth > minimum) { @@ -162,7 +163,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } // the salt is saved in the password refer to bcrypt docs - adjusted_password = await bcrypt.hash(password, 12); + password = await bcrypt.hash(password, 12); let exists; // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists @@ -171,7 +172,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? for (let tries = 0; tries < 5; tries++) { discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); + exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] }); if (!exists) break; } @@ -190,7 +191,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re const user = await new User({ created_at: new Date(), - username: adjusted_username, + username: username, discriminator, id: Snowflake.generate(), bot: false, @@ -204,12 +205,12 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re verified: false, disabled: false, deleted: false, - email: adjusted_email, + email: email, nsfw_allowed: true, // TODO: depending on age public_flags: "0", flags: "0", // TODO: generate data: { - hash: adjusted_password, + hash: password, valid_tokens_since: new Date() }, settings: { ...defaultSettings, locale: req.language || "en-US" }, @@ -220,6 +221,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); export function adjustEmail(email: string): string | undefined { + if (!email) return email; // body parser already checked if it is a valid email const parts = email.match(EMAIL_REGEX); // @ts-ignore diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 71612e31..22420983 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -8,9 +8,9 @@ import { isTextChannel } from "./messages"; const router: Router = Router(); export interface InviteCreateSchema { - target_user_id?: string | null; - target_type?: string | null; - validate?: string | null; // ? what is this + target_user_id?: string; + target_type?: string; + validate?: string; // ? what is this max_age?: number; max_uses?: number; temporary?: boolean; diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 7e4bf28a..63000b84 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -11,15 +11,15 @@ const router = Router(); export interface GuildUpdateSchema extends Omit { banner?: string | null; splash?: string | null; - description?: string | null; + description?: string; features?: string[]; verification_level?: number; default_message_notifications?: number; system_channel_flags?: number; explicit_content_filter?: number; - public_updates_channel_id?: string | null; + public_updates_channel_id?: string; afk_timeout?: number; - afk_channel_id?: string | null; + afk_channel_id?: string; preferred_locale?: string; } diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 2334bb9c..2e68d953 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -12,10 +12,10 @@ export interface GuildCreateSchema { */ name: string; region?: string; - icon?: string; + icon?: string | null; channels?: ChannelModifySchema[]; guild_template_code?: string; - system_channel_id?: string | null; + system_channel_id?: string; rules_channel_id?: string; } diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index eb3867c8..b5e243e9 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -6,7 +6,7 @@ import { DiscordApiErrors } from "@fosscord/util"; export interface GuildTemplateCreateSchema { name: string; - avatar?: string; + avatar?: string | null; } router.get("/:code", route({}), async (req: Request, res: Response) => { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index c0002d79..da2f3348 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -16,7 +16,7 @@ export interface UserModifySchema { * @maxLength 1024 */ bio?: string; - accent_color?: number | null; + accent_color?: number; banner?: string | null; password?: string; new_password?: string; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 6cd8f622..678ca64c 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -43,10 +43,37 @@ export interface RouteOptions { }; } +// Normalizer is introduced to workaround https://github.com/ajv-validator/ajv/issues/1287 +// this removes null values as ajv doesn't treat them as undefined +// normalizeBody allows to handle circular structures without issues +// taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license) +const normalizeBody = (body: any = {}) => { + const normalizedObjectsSet = new WeakSet(); + const normalizeObject = (object: any) => { + if (normalizedObjectsSet.has(object)) return; + normalizedObjectsSet.add(object); + if (Array.isArray(object)) { + for (const [index, value] of object.entries()) { + if (typeof value === "object") normalizeObject(value); + } + } else { + for (const [key, value] of Object.entries(object)) { + if (value == null) { + if (key === "icon" || key === "avatar" || key === "banner" || key === "splash") continue; + delete object[key]; + } else if (typeof value === "object") { + normalizeObject(value); + } + } + } + }; + normalizeObject(body); + return body; +}; + export function route(opts: RouteOptions) { - var validate: AnyValidateFunction; + var validate: AnyValidateFunction | undefined; if (opts.body) { - // @ts-ignore validate = ajv.getSchema(opts.body); if (!validate) throw new Error(`Body schema ${opts.body} not found`); } @@ -60,14 +87,14 @@ export function route(opts: RouteOptions) { if (!permission.has(required)) { throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); } + } - if (validate) { - const valid = validate(req.body); - if (!valid) { - const fields: Record = {}; - validate.errors?.forEach((x) => (fields[x.instancePath] = { code: x.keyword, message: x.message || "" })); - throw FieldErrors(fields); - } + if (validate) { + const valid = validate(normalizeBody(req.body)); + if (!valid) { + const fields: Record = {}; + validate.errors?.forEach((x) => (fields[x.instancePath.slice(1)] = { code: x.keyword, message: x.message || "" })); + throw FieldErrors(fields); } } next(); -- cgit 1.5.1 From 181afe7e09292f49a86f9794f9675f723378c57b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 00:19:53 +0200 Subject: :bug: fix guild create icon --- api/src/routes/guilds/index.ts | 3 ++- api/src/util/cdn.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 2e68d953..3612ca82 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; -import { route } from "@fosscord/api"; +import { handleFile, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; import { ChannelModifySchema } from "../channels/#channel_id"; @@ -34,6 +34,7 @@ router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: await Guild.insert({ name: body.name, + icon: await handleFile(`/icons/${guild_id}`, body.icon as string), region: Config.get().regions.default, owner_id: req.user_id, afk_timeout: 300, diff --git a/api/src/util/cdn.ts b/api/src/util/cdn.ts index 88b0ea0d..8c6e9ac9 100644 --- a/api/src/util/cdn.ts +++ b/api/src/util/cdn.ts @@ -42,9 +42,9 @@ export async function handleFile(path: string, body?: string): Promise Date: Sat, 18 Sep 2021 01:49:17 +0200 Subject: :pencil: add default route description to all routes --- api/src/routes/applications/detectable.ts | 5 +++-- api/src/routes/guilds/#guild_id/channels.ts | 2 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- api/src/routes/outbound-promotions.ts | 5 +++-- api/src/routes/store/applications.ts | 9 +++++---- api/src/routes/store/skus.ts | 7 ++++--- api/src/routes/users/@me/applications/#app_id/entitlements.ts | 5 +++-- api/src/routes/users/@me/billing/country-code.ts | 7 ++++--- api/src/routes/users/@me/billing/subscriptions.ts | 5 +++-- api/src/routes/users/@me/index.ts | 2 +- 10 files changed, 28 insertions(+), 21 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/applications/detectable.ts b/api/src/routes/applications/detectable.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/applications/detectable.ts +++ b/api/src/routes/applications/detectable.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index 13c6b515..a36e5448 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -5,7 +5,7 @@ import { route } from "@fosscord/api"; import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const channels = await Channel.find({ guild_id }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index bac63bd4..d1d60906 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -29,7 +29,7 @@ export type RolePositionUpdateSchema = { position: number; }[]; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; await Member.IsInGuildOrFail(req.user_id, guild_id); diff --git a/api/src/routes/outbound-promotions.ts b/api/src/routes/outbound-promotions.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/outbound-promotions.ts +++ b/api/src/routes/outbound-promotions.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/store/applications.ts b/api/src/routes/store/applications.ts index 69cd716d..352c1752 100644 --- a/api/src/routes/store/applications.ts +++ b/api/src/routes/store/applications.ts @@ -1,11 +1,12 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/applications/:id", async (req: Request, res: Response) => { - //TODO - const { id } = req.params; +router.get("/applications/:id", route({}), async (req: Request, res: Response) => { + //TODO + const { id } = req.params; res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/store/skus.ts b/api/src/routes/store/skus.ts index 5c37850d..7d0e12eb 100644 --- a/api/src/routes/store/skus.ts +++ b/api/src/routes/store/skus.ts @@ -1,11 +1,12 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/skus/:id", async (req: Request, res: Response) => { +router.get("/skus/:id", route({}), async (req: Request, res: Response) => { //TODO - const { id } = req.params; + const { id } = req.params; res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/api/src/routes/users/@me/applications/#app_id/entitlements.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/users/@me/applications/#app_id/entitlements.ts +++ b/api/src/routes/users/@me/applications/#app_id/entitlements.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/billing/country-code.ts b/api/src/routes/users/@me/billing/country-code.ts index ac3653a2..33d40796 100644 --- a/api/src/routes/users/@me/billing/country-code.ts +++ b/api/src/routes/users/@me/billing/country-code.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO - res.json({ "country_code": "US" }).status(200); + res.json({ country_code: "US" }).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/api/src/routes/users/@me/billing/subscriptions.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/users/@me/billing/subscriptions.ts +++ b/api/src/routes/users/@me/billing/subscriptions.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index da2f3348..67b11ce0 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -23,7 +23,7 @@ export interface UserModifySchema { code?: string; } -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } })); }); -- cgit 1.5.1 From a7bf2955910772409ea9c3c6c32c0394c76f34b8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 01:50:29 +0200 Subject: :sparkles: jest automatic tests --- api/jest/getRouteDescriptions.ts | 56 ++++++++++++++++++++++++++++++++++++++++ api/jest/globalSetup.js | 15 +++++++++++ api/jest/setup.js | 4 +-- api/src/util/route.ts | 5 ++-- api/tests/automatic.test.js | 2 -- api/tests/routes.test.ts | 54 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 api/jest/getRouteDescriptions.ts create mode 100644 api/jest/globalSetup.js delete mode 100644 api/tests/automatic.test.js create mode 100644 api/tests/routes.test.ts (limited to 'api/src') diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.ts new file mode 100644 index 00000000..d7d6e0c6 --- /dev/null +++ b/api/jest/getRouteDescriptions.ts @@ -0,0 +1,56 @@ +import { traverseDirectory } from "lambert-server"; +import path from "path"; +import express from "express"; +import * as RouteUtility from "../dist/util/route"; +const Router = express.Router; + +const routes = new Map(); +let currentPath = ""; +let currentFile = ""; +const methods = ["get", "post", "put", "delete", "patch"]; + +function registerPath(file, method, prefix, path, ...args) { + const urlPath = prefix + path; + const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); + const opts = args.find((x) => typeof x === "object"); + if (opts) { + routes.set(urlPath + "|" + method, opts); + // console.log(method, urlPath, opts); + } else { + console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); + } +} + +function routeOptions(opts) { + return opts; +} + +// @ts-ignore +RouteUtility.route = routeOptions; + +express.Router = (opts) => { + const path = currentPath; + const file = currentFile; + const router = Router(opts); + + for (const method of methods) { + router[method] = registerPath.bind(null, file, method, path); + } + + return router; +}; + +export default function getRouteDescriptions() { + const root = path.join(__dirname, "..", "dist", "routes", "/"); + traverseDirectory({ dirname: root, recursive: true }, (file) => { + currentFile = file; + let path = file.replace(root.slice(0, -1), ""); + path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path + path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes + if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path + currentPath = path; + + require(file); + }); + return routes; +} diff --git a/api/jest/globalSetup.js b/api/jest/globalSetup.js new file mode 100644 index 00000000..98e70fb9 --- /dev/null +++ b/api/jest/globalSetup.js @@ -0,0 +1,15 @@ +const fs = require("fs"); +const path = require("path"); +const { FosscordServer } = require("../dist/Server"); +const Server = new FosscordServer({ port: 3001 }); +global.server = Server; +module.exports = async () => { + try { + fs.unlinkSync(path.join(__dirname, "..", "database.db")); + } catch {} + return await Server.start(); +}; + +// afterAll(async () => { +// return await Server.stop(); +// }); diff --git a/api/jest/setup.js b/api/jest/setup.js index abc485ae..bd535866 100644 --- a/api/jest/setup.js +++ b/api/jest/setup.js @@ -1,2 +1,2 @@ -jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); -jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); +// jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); +// jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 678ca64c..35ea43ba 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -28,12 +28,11 @@ declare global { } } -export type RouteSchema = string; // typescript interface name -export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record }; +export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record }; export interface RouteOptions { permission?: PermissionResolvable; - body?: RouteSchema; + body?: `${string}Schema`; // typescript interface name response?: RouteResponse; example?: { body?: any; diff --git a/api/tests/automatic.test.js b/api/tests/automatic.test.js deleted file mode 100644 index 2d0a9fcb..00000000 --- a/api/tests/automatic.test.js +++ /dev/null @@ -1,2 +0,0 @@ -// TODO: check every route based on route() paramters: https://github.com/fosscord/fosscord-server/issues/308 -// TODO: check every route with different database engine diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts new file mode 100644 index 00000000..e913e0dc --- /dev/null +++ b/api/tests/routes.test.ts @@ -0,0 +1,54 @@ +// TODO: check every route based on route() parameters: https://github.com/fosscord/fosscord-server/issues/308 +// TODO: check every route with different database engine + +import getRouteDescriptions from "../jest/getRouteDescriptions"; +import supertest, { Response } from "supertest"; +import path from "path"; +import fs from "fs"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +const request = supertest("http://localhost:3001/api"); + +const SchemaPath = path.join(__dirname, "..", "assets", "responses.json"); +const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); +export const ajv = new Ajv({ + allErrors: true, + parseDate: true, + allowDate: true, + schemas, + messages: true, + strict: true, + strictRequired: true +}); +addFormats(ajv); + +describe("Automatic unit tests with route description middleware", () => { + const routes = getRouteDescriptions(); + + routes.forEach((route, pathAndMethod) => { + const [path, method] = pathAndMethod.split("|"); + test(path, (done) => { + if (!route.example) { + console.log(`Route ${path} is missing the example property`); + return done(); + } + if (!route.response) { + console.log(`Route ${path} is missing the response property`); + return done(); + } + const urlPath = path || route.example?.path; + const validate = ajv.getSchema(route.response.body); + if (!validate) return done(new Error(`Response schema ${route.response.body} not found`)); + + request[method](urlPath) + .expect(route.response.status) + .expect((err: any, res: Response) => { + if (err) return done(err); + const valid = validate(res.body); + if (!valid) return done(validate.errors); + + return done(); + }); + }); + }); +}); -- cgit 1.5.1 From a3484e64e45764775c814739efe73759d5bdeaeb Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 11:56:06 +0200 Subject: :sparkles: route middleware test option --- api/jest/getRouteDescriptions.ts | 6 ++++-- api/src/routes/users/#id/profile.ts | 2 +- api/src/util/route.ts | 6 +++--- api/tests/routes.test.ts | 11 ++++------- 4 files changed, 12 insertions(+), 13 deletions(-) (limited to 'api/src') diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.ts index d7d6e0c6..33922899 100644 --- a/api/jest/getRouteDescriptions.ts +++ b/api/jest/getRouteDescriptions.ts @@ -2,6 +2,7 @@ import { traverseDirectory } from "lambert-server"; import path from "path"; import express from "express"; import * as RouteUtility from "../dist/util/route"; +import { RouteOptions } from "../dist/util/route"; const Router = express.Router; const routes = new Map(); @@ -12,9 +13,10 @@ const methods = ["get", "post", "put", "delete", "patch"]; function registerPath(file, method, prefix, path, ...args) { const urlPath = prefix + path; const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); - const opts = args.find((x) => typeof x === "object"); + const opts: RouteOptions = args.find((x) => typeof x === "object"); if (opts) { - routes.set(urlPath + "|" + method, opts); + routes.set(urlPath + "|" + method, opts); // @ts-ignore + opts.file = sourceFile; // console.log(method, urlPath, opts); } else { console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index d60c4f86..d099bce7 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -11,7 +11,7 @@ export interface UserProfileResponse { premium_since?: Date; } -router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req: Request, res: Response) => { +router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { if (req.params.id === "@me") req.params.id = req.user_id; const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 35ea43ba..9ef92c3a 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -33,11 +33,11 @@ export type RouteResponse = { status?: number; body?: `${string}Response`; heade export interface RouteOptions { permission?: PermissionResolvable; body?: `${string}Schema`; // typescript interface name - response?: RouteResponse; - example?: { + test?: { + response?: RouteResponse; body?: any; path?: string; - event?: EventData; + event?: EventData | EventData[]; headers?: Record; }; } diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index e913e0dc..4cb7e6bc 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -3,13 +3,13 @@ import getRouteDescriptions from "../jest/getRouteDescriptions"; import supertest, { Response } from "supertest"; -import path from "path"; +import { join } from "path"; import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; const request = supertest("http://localhost:3001/api"); -const SchemaPath = path.join(__dirname, "..", "assets", "responses.json"); +const SchemaPath = join(__dirname, "..", "assets", "responses.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ allErrors: true, @@ -27,13 +27,10 @@ describe("Automatic unit tests with route description middleware", () => { routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); + test(path, (done) => { if (!route.example) { - console.log(`Route ${path} is missing the example property`); - return done(); - } - if (!route.response) { - console.log(`Route ${path} is missing the response property`); + console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); } const urlPath = path || route.example?.path; -- cgit 1.5.1 From 55ae0d6ae9289aab06a68033bb5782adcba74485 Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Fri, 17 Sep 2021 15:35:35 +0200 Subject: Add /users/@me/connections --- api/src/routes/users/@me/connections.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 api/src/routes/users/@me/connections.ts (limited to 'api/src') diff --git a/api/src/routes/users/@me/connections.ts b/api/src/routes/users/@me/connections.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/users/@me/connections.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file -- cgit 1.5.1 From e628c92a440ee43f19f02a992b8b10575a8a6978 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:24:42 +0200 Subject: Update connections.ts --- api/src/routes/users/@me/connections.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/connections.ts b/api/src/routes/users/@me/connections.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/users/@me/connections.ts +++ b/api/src/routes/users/@me/connections.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; -- cgit 1.5.1 From 0d3562de76db5cc003fc55a8f92705ba5bd54540 Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Sat, 18 Sep 2021 16:13:15 +0000 Subject: Add request logging (with env var: log-requests) Request logging is enabled when the log-request environment variable is set. --- api/package-lock.json | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++ api/package.json | 2 + api/src/Server.ts | 9 ++++ 3 files changed, 141 insertions(+) (limited to 'api/src') diff --git a/api/package-lock.json b/api/package-lock.json index fd9c68b4..499c612e 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "@fosscord/util": "file:../util", + "@types/morgan": "^1.9.3", "ajv": "8.6.2", "ajv-formats": "^2.1.1", "amqplib": "^0.8.0", @@ -34,6 +35,7 @@ "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", + "morgan": "^1.10.0", "multer": "^1.4.2", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", @@ -1452,6 +1454,14 @@ "@types/node": "*" } }, + "node_modules/@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/multer": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", @@ -2271,6 +2281,22 @@ } ] }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/bcrypt": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", @@ -8021,6 +8047,42 @@ "mongoose": "*" } }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "node_modules/morphdom": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.6.1.tgz", @@ -8582,6 +8644,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/on-net-listen": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/on-net-listen/-/on-net-listen-1.1.2.tgz", @@ -14113,6 +14183,14 @@ } } }, + "@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "requires": { + "@types/node": "*" + } + }, "@types/multer": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", @@ -14793,6 +14871,21 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "bcrypt": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", @@ -19500,6 +19593,38 @@ "integrity": "sha512-5gTjPH6HUmtNhamv8MPwExWo01Z4d9CT5njZlupqqbmxzMXTbDOgCuP/jnK+9SV0Fs7nuyYlXv7pJ/nA2pAAuA==", "requires": {} }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "morphdom": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.6.1.tgz", @@ -19964,6 +20089,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "on-net-listen": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/on-net-listen/-/on-net-listen-1.1.2.tgz", diff --git a/api/package.json b/api/package.json index ad959e57..98b15787 100644 --- a/api/package.json +++ b/api/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@fosscord/util": "file:../util", + "@types/morgan": "^1.9.3", "ajv": "8.6.2", "ajv-formats": "^2.1.1", "amqplib": "^0.8.0", @@ -85,6 +86,7 @@ "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", + "morgan": "^1.10.0", "multer": "^1.4.2", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", diff --git a/api/src/Server.ts b/api/src/Server.ts index b9ca3fba..318d7094 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -11,6 +11,7 @@ import path from "path"; import { initRateLimits } from "./middlewares/RateLimit"; import TestClient from "./middlewares/TestClient"; import { initTranslation } from "./middlewares/Translation"; +import morgan from "morgan"; export interface FosscordServerOptions extends ServerOptions {} @@ -36,6 +37,11 @@ export class FosscordServer extends Server { await Config.init(); await initEvent(); + let logRequests = process.env["log-requests"] != undefined; + if(logRequests) { + this.app.use(morgan("combined")); + } + this.app.use(CORS); this.app.use(BodyParser({ inflate: true, limit: "10mb" })); @@ -65,6 +71,9 @@ export class FosscordServer extends Server { this.app.use(ErrorHandler); TestClient(this.app); + if(logRequests){ + console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'log-requests' environment variable!"); + } return super.start(); } } -- cgit 1.5.1 From ada95b6c3938f9aa644aebc2bb26dbb073aa3324 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Sat, 18 Sep 2021 18:36:29 +0200 Subject: Removed ChannelService, more fixes --- api/src/routes/channels/#channel_id/index.ts | 4 +- .../routes/channels/#channel_id/messages/index.ts | 6 +- api/src/routes/channels/#channel_id/recipients.ts | 6 +- api/src/routes/users/@me/channels.ts | 4 +- gateway/src/listener/listener.ts | 2 +- util/src/entities/Channel.ts | 124 +++++++++++++++++++-- util/src/index.ts | 1 - util/src/services/ChannelService.ts | 118 -------------------- util/src/services/index.ts | 1 - util/src/util/Array.ts | 3 + util/src/util/index.ts | 1 + 11 files changed, 131 insertions(+), 139 deletions(-) delete mode 100644 util/src/services/ChannelService.ts delete mode 100644 util/src/services/index.ts create mode 100644 util/src/util/Array.ts (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 70dd3994..3f434f5e 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -28,7 +28,7 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request ]); } else if (channel.type === ChannelType.GROUP_DM) { - await ChannelService.removeRecipientFromChannel(channel, req.user_id) + await Channel.removeRecipientFromChannel(channel, req.user_id) } else { //TODO messages in this channel should be deleted before deleting the channel await Promise.all([ diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index bb610a6a..cde14164 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent, Recipient } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import multer from "multer"; @@ -150,7 +150,6 @@ router.post( return res.status(400).json(error); } } - //TODO querying the DB at every message post should be avoided, caching maybe? const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }) const embeds = []; @@ -184,7 +183,8 @@ router.post( } } - await Promise.all(channel.recipients!.map(async r => { + //Only one recipients should be closed here, since in group DMs the recipient is deleted not closed + await Promise.all(channel.recipients!.filter(r => r.closed).map(async r => { r.closed = false; return await r.save() })); diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index d88b38f3..c7beeee8 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Channel, ChannelRecipientAddEvent, ChannelService, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; +import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; const router: Router = Router(); @@ -13,7 +13,7 @@ router.put("/:user_id", async (req: Request, res: Response) => { user_id ].unique() - const new_channel = await ChannelService.createDMChannel(recipients, req.user_id) + const new_channel = await Channel.createDMChannel(recipients, req.user_id) return res.status(201).json(new_channel); } else { if (channel.recipients!.map(r => r.user_id).includes(user_id)) { @@ -49,7 +49,7 @@ router.delete("/:user_id", async (req: Request, res: Response) => { throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error? } - await ChannelService.removeRecipientFromChannel(channel, user_id) + await Channel.removeRecipientFromChannel(channel, user_id) return res.sendStatus(204); }); diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 873ff245..b5782eca 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Recipient, ChannelService, DmChannelDTO } from "@fosscord/util"; +import { Recipient, DmChannelDTO, Channel } from "@fosscord/util"; import { route } from "@fosscord/api"; const router: Router = Router(); @@ -16,7 +16,7 @@ export interface DmChannelCreateSchema { router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; - res.json(await ChannelService.createDMChannel(body.recipients, req.user_id, body.name)); + res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); }); export default router; diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 35841312..ae13cca7 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -32,7 +32,7 @@ export async function setupListener(this: WebSocket) { }); const guilds = members.map((x) => x.guild); const recipients = await Recipient.find({ - where: { user_id: this.user_id }, + where: { user_id: this.user_id, closed: false }, relations: ["channel"], }); const dm_channels = recipients.map((x) => x.channel); diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index aa2bfab3..ea632778 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,11 +1,13 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { User } from "./User"; +import { PublicUserProjection, User } from "./User"; import { HTTPError } from "lambert-server"; -import { emitEvent, getPermission, Snowflake } from "../util"; -import { ChannelCreateEvent } from "../interfaces"; +import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial } from "../util"; +import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; import { Recipient } from "./Recipient"; +import { DmChannelDTO } from "../dtos"; +import { Message } from "./Message"; export enum ChannelType { GUILD_TEXT = 0, // a text channel within a server @@ -97,7 +99,6 @@ export class Channel extends BaseClass { @Column({ nullable: true }) topic?: string; - // TODO: DM channel static async createChannel( channel: Partial, user_id: string = "0", @@ -150,16 +151,123 @@ export class Channel extends BaseClass { new Channel(channel).save(), !opts?.skipEventEmit ? emitEvent({ - event: "CHANNEL_CREATE", - data: channel, - guild_id: channel.guild_id, - } as ChannelCreateEvent) + event: "CHANNEL_CREATE", + data: channel, + guild_id: channel.guild_id, + } as ChannelCreateEvent) : Promise.resolve(), ]); return channel; } + static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { + recipients = recipients.unique().filter((x) => x !== creator_user_id); + const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + + // TODO: check config for max number of recipients + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + + const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; + + let channel = null; + + const channelRecipients = [...recipients, creator_user_id] + + const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] }) + + for (let ur of userRecipients) { + let re = ur.channel.recipients!.map(r => r.user_id) + if (re.length === channelRecipients.length) { + if (containsAll(re, channelRecipients)) { + if (channel == null) { + channel = ur.channel + await ur.assign({ closed: false }).save() + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await new Channel({ + name, + type, + owner_id: (type === ChannelType.DM ? undefined : creator_user_id), + created_at: new Date(), + last_message_id: null, + recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })), + }).save(); + } + + + const channel_dto = await DmChannelDTO.from(channel) + + if (type === ChannelType.GROUP_DM) { + + for (let recipient of channel.recipients!) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }) + } + } else { + await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); + } + + return channel_dto.excludedRecipients([creator_user_id]) + } + + static async removeRecipientFromChannel(channel: Channel, user_id: string) { + await Recipient.delete({ channel_id: channel.id, user_id: user_id }) + channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id) + + if (channel.recipients?.length === 0) { + await Channel.deleteChannel(channel); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + return + } + + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + //If the owner leave we make the first recipient in the list the new owner + if (channel.owner_id === user_id) { + channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? + await emitEvent({ + event: "CHANNEL_UPDATE", + data: await DmChannelDTO.from(channel, [user_id]), + channel_id: channel.id + }); + } + + await channel.save() + + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", data: { + channel_id: channel.id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, channel_id: channel.id + } as ChannelRecipientRemoveEvent); + } + + static async deleteChannel(channel: Channel) { + await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util + //TODO before deleting the channel we should check and delete other relations + await Channel.delete({ id: channel.id }) + } + isDm() { return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM } diff --git a/util/src/index.ts b/util/src/index.ts index 538bfdd1..fc00d46b 100644 --- a/util/src/index.ts +++ b/util/src/index.ts @@ -4,7 +4,6 @@ import "reflect-metadata"; export * from "./util/index"; export * from "./interfaces/index"; export * from "./entities/index"; -export * from "./services/index"; export * from "./dtos/index"; // import Config from "../util/Config"; diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts deleted file mode 100644 index aa021a4a..00000000 --- a/util/src/services/ChannelService.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Channel, ChannelType, Message, PublicUserProjection, Recipient, User } from "../entities"; -import { HTTPError } from "lambert-server"; -import { emitEvent, trimSpecial } from "../util"; -import { DmChannelDTO } from "../dtos"; -import { ChannelRecipientRemoveEvent } from "../interfaces"; - -export function checker(arr: any[], target: any[]) { - return target.every(v => arr.includes(v)); -} - -export class ChannelService { - public static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { - recipients = recipients.unique().filter((x) => x !== creator_user_id); - const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); - - // TODO: check config for max number of recipients - if (otherRecipientsUsers.length !== recipients.length) { - throw new HTTPError("Recipient/s not found"); - } - - const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; - - let channel = null; - - const channelRecipients = [...recipients, creator_user_id] - - const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] }) - - for (let ur of userRecipients) { - let re = ur.channel.recipients!.map(r => r.user_id) - if (re.length === channelRecipients.length) { - if (checker(re, channelRecipients)) { - if (channel == null) { - channel = ur.channel - await ur.assign({ closed: false }).save() - } - } - } - } - - if (channel == null) { - name = trimSpecial(name); - - channel = await new Channel({ - name, - type, - owner_id: (type === ChannelType.DM ? undefined : creator_user_id), - created_at: new Date(), - last_message_id: null, - recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })), - }).save(); - } - - - const channel_dto = await DmChannelDTO.from(channel) - - if (type === ChannelType.GROUP_DM) { - - for (let recipient of channel.recipients!) { - await emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id - }) - } - } else { - await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); - } - - return channel_dto.excludedRecipients([creator_user_id]) - } - - public static async removeRecipientFromChannel(channel: Channel, user_id: string) { - await Recipient.delete({ channel_id: channel.id, user_id: user_id }) - channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id) - - if (channel.recipients?.length === 0) { - await ChannelService.deleteChannel(channel); - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id - }); - return - } - - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id - }); - - //If the owner leave we make the first recipient in the list the new owner - if (channel.owner_id === user_id) { - channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? - await emitEvent({ - event: "CHANNEL_UPDATE", - data: await DmChannelDTO.from(channel, [user_id]), - channel_id: channel.id - }); - } - - await channel.save() - - await emitEvent({ - event: "CHANNEL_RECIPIENT_REMOVE", data: { - channel_id: channel.id, - user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) - }, channel_id: channel.id - } as ChannelRecipientRemoveEvent); - } - - public static async deleteChannel(channel: Channel) { - await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util - //TODO before deleting the channel we should check and delete other relations - await Channel.delete({ id: channel.id }) - } -} diff --git a/util/src/services/index.ts b/util/src/services/index.ts deleted file mode 100644 index c012a208..00000000 --- a/util/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ChannelService"; diff --git a/util/src/util/Array.ts b/util/src/util/Array.ts new file mode 100644 index 00000000..27f7c961 --- /dev/null +++ b/util/src/util/Array.ts @@ -0,0 +1,3 @@ +export function containsAll(arr: any[], target: any[]) { + return target.every(v => arr.includes(v)); +} \ No newline at end of file diff --git a/util/src/util/index.ts b/util/src/util/index.ts index 4e92f017..2de26fc7 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts @@ -12,3 +12,4 @@ export * from "./RabbitMQ"; export * from "./Regex"; export * from "./Snowflake"; export * from "./String"; +export * from "./Array"; -- cgit 1.5.1 From 0d41e18065131958de98c6dc86a564893115ebff Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Sat, 18 Sep 2021 18:43:00 +0000 Subject: Add response code white/blacklisting --- api/src/Server.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/Server.ts b/api/src/Server.ts index 318d7094..bda19db5 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -1,3 +1,4 @@ +import { OptionsJson } from 'body-parser'; import "missing-native-js-functions"; import { Connection } from "mongoose"; import { Server, ServerOptions } from "lambert-server"; @@ -37,9 +38,27 @@ export class FosscordServer extends Server { await Config.init(); await initEvent(); + + /* + DOCUMENTATION: uses log-requests environment variable + + # only log 200 and 204 + log-requests=200 204 + # log everything except 200 and 204 + log-requests=-200 204 + # log all requests + log-requests=- + */ + let logRequests = process.env["log-requests"] != undefined; if(logRequests) { - this.app.use(morgan("combined")); + this.app.use(morgan("combined", { + skip: (req, res) => { + var skip = !(process.env["log-requests"]?.includes(res.statusCode.toString()) ?? false); + if(process.env["log-requests"]?.charAt(0) == '-') skip = !skip; + return skip; + } + })); } this.app.use(CORS); -- cgit 1.5.1 From 157f29536ac43c2d423e218eea78ee58bf7e80f6 Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Sat, 18 Sep 2021 21:01:06 +0200 Subject: Fix environment variable name to work on non-Windows platforms --- api/src/Server.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'api/src') diff --git a/api/src/Server.ts b/api/src/Server.ts index bda19db5..4a226d12 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -40,22 +40,22 @@ export class FosscordServer extends Server { /* - DOCUMENTATION: uses log-requests environment variable + DOCUMENTATION: uses LOG_REQUESTS environment variable # only log 200 and 204 - log-requests=200 204 + LOG_REQUESTS=200 204 # log everything except 200 and 204 - log-requests=-200 204 + LOG_REQUESTS=-200 204 # log all requests - log-requests=- + LOG_REQUESTS=- */ - - let logRequests = process.env["log-requests"] != undefined; + + let logRequests = process.env["LOG_REQUESTS"] != undefined; if(logRequests) { this.app.use(morgan("combined", { skip: (req, res) => { - var skip = !(process.env["log-requests"]?.includes(res.statusCode.toString()) ?? false); - if(process.env["log-requests"]?.charAt(0) == '-') skip = !skip; + var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + if(process.env["LOG_REQUESTS"]?.charAt(0) == '-') skip = !skip; return skip; } })); @@ -91,7 +91,7 @@ export class FosscordServer extends Server { TestClient(this.app); if(logRequests){ - console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'log-requests' environment variable!"); + console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!"); } return super.start(); } -- cgit 1.5.1 From 75e948b62bc6bee57d34c0b5a6122043c881fbd7 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:43:25 +0200 Subject: :art: user is verified by default --- api/src/routes/auth/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index efe91625..c0b0e18a 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -202,7 +202,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re premium_type: 2, bio: "", mfa_enabled: false, - verified: false, + verified: true, disabled: false, deleted: false, email: email, -- cgit 1.5.1 From c50648ddccfef4f06dbd46fb8aaede9f9709e6e8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:44:49 +0200 Subject: :truck: move handleFile to cdn --- api/src/routes/channels/#channel_id/messages/index.ts | 3 +-- api/src/routes/guilds/#guild_id/index.ts | 3 +-- api/src/routes/guilds/index.ts | 5 ++--- api/src/routes/users/@me/index.ts | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index ec93649e..07bfc22d 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,10 +1,9 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, Embed, getPermission, Message, uploadFile } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; import multer from "multer"; import { sendMessage } from "@fosscord/api"; -import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 63000b84..d8ee86ff 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; +import { emitEvent, getPermission, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; -import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; import { GuildCreateSchema } from "../index"; diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 3612ca82..abde147d 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,7 +1,6 @@ import { Router, Request, Response } from "express"; -import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; -import { handleFile, route } from "@fosscord/api"; -import { DiscordApiErrors } from "@fosscord/util"; +import { Role, Guild, Snowflake, Config, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { ChannelModifySchema } from "../channels/#channel_id"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index da2f3348..835aab4d 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,7 +1,6 @@ import { Router, Request, Response } from "express"; -import { User, PrivateUserProjection, emitEvent, UserUpdateEvent } from "@fosscord/util"; +import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { handleFile } from "@fosscord/api"; const router: Router = Router(); -- cgit 1.5.1 From 994406e5cea886444838ce71c4aaee46663be1a9 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:46:22 +0200 Subject: :art: remove deleteMessageAttachments and move to entity --- .../channels/#channel_id/messages/#message_id/index.ts | 3 --- api/src/util/Attachments.ts | 12 ------------ api/src/util/index.ts | 1 - util/src/entities/Attachment.ts | 9 ++++++++- 4 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 api/src/util/Attachments.ts (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index b5220fab..7f7de264 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -3,7 +3,6 @@ import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; import { handleMessage, postHandleMessage } from "@fosscord/api"; import { MessageCreateSchema } from "../index"; -import { deleteMessageAttachments } from "@fosscord/api/util/Attachments"; const router = Router(); // TODO: message content/embed string length limit @@ -34,7 +33,6 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE }); await Promise.all([ - await deleteMessageAttachments(message_id, new_message.attachments), //This delete all the attachments not in the array new_message!.save(), await emitEvent({ event: "MESSAGE_UPDATE", @@ -60,7 +58,6 @@ router.delete("/", route({}), async (req: Request, res: Response) => { permission.hasThrow("MANAGE_MESSAGES"); } - await deleteMessageAttachments(message_id); await Message.delete({ id: message_id }); await emitEvent({ diff --git a/api/src/util/Attachments.ts b/api/src/util/Attachments.ts deleted file mode 100644 index addda97f..00000000 --- a/api/src/util/Attachments.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Attachment } from "@fosscord/util"; -import { deleteFile } from "@fosscord/api"; -import { URL } from "url"; - -export async function deleteMessageAttachments(messageId: string, keep?: Attachment[]) { - let attachments = await Attachment.find({ message_id: messageId }); - if (keep) - attachments = attachments.filter(x => !keep.map(k => k.id).includes(x.id)); - await Promise.all(attachments.map(a => a.remove())); - - attachments.forEach(a => deleteFile((new URL(a.url)).pathname)); //We don't need to await since this is done on the cdn -} diff --git a/api/src/util/index.ts b/api/src/util/index.ts index 4b1e8e77..3e47ce4e 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -1,5 +1,4 @@ export * from "./Base64"; -export * from "./cdn"; export * from "./FieldError"; export * from "./ipAddress"; export * from "./Message"; diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts index ca893400..82c5ecf5 100644 --- a/util/src/entities/Attachment.ts +++ b/util/src/entities/Attachment.ts @@ -1,4 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { URL } from "url"; +import { deleteFile } from "../util/cdn"; import { BaseClass } from "./BaseClass"; @Entity("attachments") @@ -31,4 +33,9 @@ export class Attachment extends BaseClass { @JoinColumn({ name: "message_id" }) @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments) message: import("./Message").Message; + + @BeforeRemove() + onDelete() { + return deleteFile(new URL(this.url).pathname); + } } -- cgit 1.5.1 From 12a151eb2d907bdcfa80baa0defe3a255d58f8a7 Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Mon, 20 Sep 2021 08:58:06 -0400 Subject: Delete messages before deleting channel Might need some testing, but seems pretty solid for one line of code lol --- api/src/routes/channels/#channel_id/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 3f434f5e..ca8cc87c 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, Message, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -30,8 +30,8 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request } else if (channel.type === ChannelType.GROUP_DM) { await Channel.removeRecipientFromChannel(channel, req.user_id) } else { - //TODO messages in this channel should be deleted before deleting the channel await Promise.all([ + Message.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); -- cgit 1.5.1 From ce09e01c2154072dfb52fceaca33a6106d2e1220 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:11:22 +0200 Subject: :construction: auto delete relations --- api/src/routes/channels/#channel_id/index.ts | 20 ++++-- util/package-lock.json | 98 ++++++++++++++-------------- util/src/entities/BaseClass.ts | 49 +++++++++++++- util/src/entities/Channel.ts | 12 +--- util/src/util/Database.ts | 2 +- 5 files changed, 112 insertions(+), 69 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 3f434f5e..1063b151 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,6 +1,15 @@ -import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { + Channel, + ChannelDeleteEvent, + ChannelPermissionOverwriteType, + ChannelType, + ChannelUpdateEvent, + emitEvent, + Recipient, + handleFile +} from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { handleFile, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel @@ -20,15 +29,14 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); if (channel.type === ChannelType.DM) { - const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }) - recipient.closed = true + const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }); + recipient.closed = true; await Promise.all([ recipient.save(), emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent) ]); - } else if (channel.type === ChannelType.GROUP_DM) { - await Channel.removeRecipientFromChannel(channel, req.user_id) + await Channel.removeRecipientFromChannel(channel, req.user_id); } else { //TODO messages in this channel should be deleted before deleting the channel await Promise.all([ diff --git a/util/package-lock.json b/util/package-lock.json index e5a62d0f..f4129614 100644 --- a/util/package-lock.json +++ b/util/package-lock.json @@ -226,19 +226,19 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.15.4", "@babel/helper-replace-supers": "^7.15.4", "@babel/helper-simple-access": "^7.15.4", "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.15.7", "@babel/template": "^7.15.4", "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/types": "^7.15.6" }, "engines": { "node": ">=6.9.0" @@ -305,9 +305,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -422,9 +422,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", - "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1204,9 +1204,9 @@ } }, "node_modules/@types/node": { - "version": "14.17.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.16.tgz", - "integrity": "sha512-WiFf2izl01P1CpeY8WqFAeKWwByMueBEkND38EcN8N68qb0aDG3oIS1P5MhAX5kUdr469qRyqsY/MjanLjsFbQ==" + "version": "14.17.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.17.tgz", + "integrity": "sha512-niAjcewgEYvSPCZm3OaM9y6YQrL2SEPH9PymtE6fuZAvFiP6ereCcvApGl2jKTq7copTIguX3PBvfP08LN4LvQ==" }, "node_modules/@types/node-fetch": { "version": "2.5.12", @@ -1941,9 +1941,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001257", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", - "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", + "version": "1.0.30001258", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001258.tgz", + "integrity": "sha512-RBByOG6xWXUp0CR2/WU2amXz3stjKpSl5J1xU49F1n2OxD//uBZO4wCKUiG+QMGf7CHGfDDcqoKriomoGVxTeA==", "dev": true, "funding": { "type": "opencollective", @@ -2498,9 +2498,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.3.840", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz", - "integrity": "sha512-yRoUmTLDJnkIJx23xLY7GbSvnmDCq++NSuxHDQ0jiyDJ9YZBUGJcrdUqm+ZwZFzMbCciVzfem2N2AWiHJcWlbw==", + "version": "1.3.843", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.843.tgz", + "integrity": "sha512-OWEwAbzaVd1Lk9MohVw8LxMXFlnYd9oYTYxfX8KS++kLLjDfbovLOcEEXwRhG612dqGQ6+44SZvim0GXuBRiKg==", "dev": true }, "node_modules/emittery": { @@ -6249,13 +6249,12 @@ } }, "node_modules/stack-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.4.tgz", - "integrity": "sha512-ERg+H//lSSYlZhBIUu+wJnqg30AbyBbpZlIhcshpn7BNzpoRODZgfyr9J+8ERf3ooC6af3u7Lcl01nleau7MrA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", "dev": true, "dependencies": { - "escape-string-regexp": "^2.0.0", - "source-map-support": "^0.5.20" + "escape-string-regexp": "^2.0.0" }, "engines": { "node": ">=10" @@ -7535,19 +7534,19 @@ } }, "@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.15.4", "@babel/helper-replace-supers": "^7.15.4", "@babel/helper-simple-access": "^7.15.4", "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.15.7", "@babel/template": "^7.15.4", "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/types": "^7.15.6" } }, "@babel/helper-optimise-call-expression": { @@ -7596,9 +7595,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { @@ -7688,9 +7687,9 @@ } }, "@babel/parser": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", - "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -8336,9 +8335,9 @@ } }, "@types/node": { - "version": "14.17.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.16.tgz", - "integrity": "sha512-WiFf2izl01P1CpeY8WqFAeKWwByMueBEkND38EcN8N68qb0aDG3oIS1P5MhAX5kUdr469qRyqsY/MjanLjsFbQ==" + "version": "14.17.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.17.tgz", + "integrity": "sha512-niAjcewgEYvSPCZm3OaM9y6YQrL2SEPH9PymtE6fuZAvFiP6ereCcvApGl2jKTq7copTIguX3PBvfP08LN4LvQ==" }, "@types/node-fetch": { "version": "2.5.12", @@ -8945,9 +8944,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001257", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", - "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", + "version": "1.0.30001258", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001258.tgz", + "integrity": "sha512-RBByOG6xWXUp0CR2/WU2amXz3stjKpSl5J1xU49F1n2OxD//uBZO4wCKUiG+QMGf7CHGfDDcqoKriomoGVxTeA==", "dev": true }, "caseless": { @@ -9405,9 +9404,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.840", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz", - "integrity": "sha512-yRoUmTLDJnkIJx23xLY7GbSvnmDCq++NSuxHDQ0jiyDJ9YZBUGJcrdUqm+ZwZFzMbCciVzfem2N2AWiHJcWlbw==", + "version": "1.3.843", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.843.tgz", + "integrity": "sha512-OWEwAbzaVd1Lk9MohVw8LxMXFlnYd9oYTYxfX8KS++kLLjDfbovLOcEEXwRhG612dqGQ6+44SZvim0GXuBRiKg==", "dev": true }, "emittery": { @@ -12300,13 +12299,12 @@ } }, "stack-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.4.tgz", - "integrity": "sha512-ERg+H//lSSYlZhBIUu+wJnqg30AbyBbpZlIhcshpn7BNzpoRODZgfyr9J+8ERf3ooC6af3u7Lcl01nleau7MrA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", "dev": true, "requires": { - "escape-string-regexp": "^2.0.0", - "source-map-support": "^0.5.20" + "escape-string-regexp": "^2.0.0" } }, "statuses": { diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 9b2ce058..2a621f40 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -1,5 +1,14 @@ import "reflect-metadata"; -import { BaseEntity, BeforeInsert, BeforeUpdate, EntityMetadata, FindConditions, PrimaryColumn } from "typeorm"; +import { + BaseEntity, + BeforeInsert, + BeforeUpdate, + EntityMetadata, + FindConditions, + getConnection, + PrimaryColumn, + RemoveOptions, +} from "typeorm"; import { Snowflake } from "../util/Snowflake"; import "missing-native-js-functions"; @@ -69,6 +78,44 @@ export class BaseClassWithoutId extends BaseEntity { const repository = this.getRepository(); return repository.decrement(conditions, propertyPath, value); } + + static async delete(criteria: FindConditions, options?: RemoveOptions) { + if (!criteria) throw new Error("You need to specify delete criteria"); + + const repository = this.getRepository(); + const promises = repository.metadata.relations.map((x) => { + if (x.orphanedRowAction !== "delete") return; + if (typeof x.type === "string") return; + + const foreignKey = + x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || + x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity + if (!foreignKey) { + throw new Error( + `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` + ); + } + console.log(foreignKey); + const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; + if (!id) throw new Error("id missing in criteria options"); + + if (x.relationType === "many-to-many" || x.relationType === "one-to-many") { + return getConnection() + .createQueryBuilder() + .relation(this, x.propertyName) + .of(id) + .remove({ [foreignKey.columnNames[0]]: id }); + } else if (x.relationType === "one-to-one" || x.relationType === "many-to-one") { + return getConnection() + .createQueryBuilder() + .from(x.inverseEntityMetadata, "user") + .of(id) + .remove({ [foreignKey.name]: id }); + } + }); + await Promise.all(promises); + return super.delete(criteria, options); + } } export class BaseClass extends BaseClassWithoutId { diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index b1f75f33..74611eea 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,14 +1,4 @@ -import { - Column, - Entity, - FindConditions, - JoinColumn, - ManyToOne, - ObjectID, - OneToMany, - RelationId, - RemoveOptions, -} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; import { PublicUserProjection, User } from "./User"; diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index d3844cd9..c22d8abd 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -21,7 +21,7 @@ export function initDatabase() { // entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"), synchronize: true, - logging: false, + logging: true, cache: { duration: 1000 * 3, // cache all find queries for 3 seconds }, -- cgit 1.5.1 From 013a4b8bc8d50b0df21381628bfe61db4ef927f6 Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Mon, 20 Sep 2021 11:29:37 -0400 Subject: Fix one thing, another problem pops up Co-authored-by: TheArcaneBrony --- api/src/routes/channels/#channel_id/index.ts | 5 ++++- api/src/routes/users/@me/relationships.ts | 2 +- api/src/util/route.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index ca8cc87c..9a3cb4c2 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, Message, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, Message, Invite, ReadState, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -32,6 +32,9 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request } else { await Promise.all([ Message.delete({ channel_id: channel_id }), + Invite.delete({ channel_id: channel_id }), + Recipient.delete({ channel_id: channel_id }), + ReadState.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 1d72f11a..6ad873a6 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -59,7 +59,7 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, relations: ["relationships", "relationships.to"], select: userProjection, where: { - discriminator: String(req.body.discriminator,).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes + discriminator: String(req.body.discriminator).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes username: req.body.username } }), diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 678ca64c..b7e6296b 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -14,6 +14,7 @@ export const ajv = new Ajv({ parseDate: true, allowDate: true, schemas, + coerceTypes: true, messages: true, strict: true, strictRequired: true -- cgit 1.5.1 From dd9ec0c6a0dc3fb87df635ca64c40fab598f753e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:38:16 +0200 Subject: :sparkles: accept invite page --- api/assets/schemas.json | 1 - api/client_test/index.html | 2 +- api/src/routes/auth/register.ts | 36 ++++++++++++++++++++++-------------- api/src/routes/invites/index.ts | 9 ++------- util/src/entities/Config.ts | 10 ++++++---- util/src/entities/Invite.ts | 10 ++++++++++ 6 files changed, 41 insertions(+), 27 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index bfe6092b..da193b28 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -38,7 +38,6 @@ "additionalProperties": false, "required": [ "consent", - "password", "username" ], "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/api/client_test/index.html b/api/client_test/index.html index 9a0aeae1..ebe92e4c 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -19,7 +19,7 @@ ASSET_ENDPOINT: "", MEDIA_PROXY_ENDPOINT: "https://media.discordapp.net", WIDGET_ENDPOINT: `//${location.host}/widget`, - INVITE_HOST: `${location.hostname}`, + INVITE_HOST: `${location.host}/invite`, GUILD_TEMPLATE_HOST: "discord.new", GIFT_CODE_HOST: "discord.gift", RELEASE_CHANNEL: "stable", diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index c0b0e18a..4d3f2860 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config, defaultSettings, Member, Invite } from "@fosscord/util"; import bcrypt from "bcrypt"; import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api"; import "missing-native-js-functions"; @@ -19,7 +19,7 @@ export interface RegisterSchema { * @minLength 1 * @maxLength 72 */ - password: string; // TODO: use password strength of config + password?: string; consent: boolean; /** * @TJS-format email @@ -60,7 +60,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } console.log("register", req.body.email, req.body.username, ip); - // TODO: automatically join invite // TODO: gift_code_sku_id? // TODO: check password strength @@ -87,13 +86,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); } - // require invite to register -> e.g. for organizations to send invites to their employees - if (register.requireInvite && !invite) { - throw FieldErrors({ - email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } - }); - } - if (email) { // replace all dots and chars after +, if its a gmail.com email if (!email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); @@ -109,13 +101,13 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } }); } - } else if (register.email.necessary) { + } else if (register.email.required) { throw FieldErrors({ email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } }); } - if (register.dateOfBirth.necessary && !date_of_birth) { + if (register.dateOfBirth.required && !date_of_birth) { throw FieldErrors({ date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } }); @@ -162,8 +154,14 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re // TODO: check captcha } - // the salt is saved in the password refer to bcrypt docs - password = await bcrypt.hash(password, 12); + if (password) { + // the salt is saved in the password refer to bcrypt docs + password = await bcrypt.hash(password, 12); + } else if (register.password.required) { + throw FieldErrors({ + password: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); + } let exists; // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists @@ -217,6 +215,16 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re fingerprints: [] }).save(); + if (invite) { + // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible) + await Invite.joinGuild(user.id, invite); + } else if (register.requireInvite) { + // require invite to register -> e.g. for organizations to send invites to their employees + throw FieldErrors({ + email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } + }); + } + return res.json({ token: await generateToken(user.id) }); }); diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index ae8a5944..0fcf7c86 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -15,14 +15,9 @@ router.get("/:code", route({}), async (req: Request, res: Response) => { router.post("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; + const invite = await Invite.joinGuild(req.user_id, code); - const invite = await Invite.findOneOrFail({ code }); - if (invite.uses++ >= invite.max_uses) await Invite.delete({ code }); - else await invite.save(); - - await Member.addToGuild(req.user_id, invite.guild_id); - - res.status(200).send(invite); + res.json(invite); }); // * cant use permission of route() function because path doesn't have guild_id/channel_id diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index fd830db8..f969b6bb 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -110,13 +110,13 @@ export interface ConfigValue { }; register: { email: { - necessary: boolean; // we have to use necessary instead of required as the cli tool uses json schema and can't use required + required: boolean; allowlist: boolean; blocklist: boolean; domains: string[]; }; dateOfBirth: { - necessary: boolean; + required: boolean; minimum: number; // in years }; requireCaptcha: boolean; @@ -125,6 +125,7 @@ export interface ConfigValue { allowMultipleAccounts: boolean; blockProxies: boolean; password: { + required: boolean; minLength: number; minNumbers: number; minUpperCase: number; @@ -246,14 +247,14 @@ export const DefaultConfigOptions: ConfigValue = { }, register: { email: { - necessary: true, + required: false, 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, + required: false, minimum: 13, }, requireInvite: false, @@ -262,6 +263,7 @@ export const DefaultConfigOptions: ConfigValue = { allowMultipleAccounts: true, blockProxies: true, password: { + required: false, minLength: 8, minNumbers: 2, minUpperCase: 2, diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts index afad9c02..1396004e 100644 --- a/util/src/entities/Invite.ts +++ b/util/src/entities/Invite.ts @@ -1,4 +1,5 @@ import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm"; +import { Member } from "."; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; @@ -63,4 +64,13 @@ export class Invite extends BaseClass { @Column({ nullable: true }) target_user_type?: number; + + static async joinGuild(user_id: string, code: string) { + const invite = await Invite.findOneOrFail({ code }); + if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); + else await invite.save(); + + await Member.addToGuild(user_id, invite.guild_id); + return invite; + } } -- cgit 1.5.1 From a4811d86ce0ad31639f33620646c6bd9f4c6b661 Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Mon, 20 Sep 2021 12:16:17 -0400 Subject: oRdEr Of OpErAtIoNs --- api/src/routes/channels/#channel_id/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 9a3cb4c2..dcbf6d4e 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -31,10 +31,10 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request await Channel.removeRecipientFromChannel(channel, req.user_id) } else { await Promise.all([ - Message.delete({ channel_id: channel_id }), Invite.delete({ channel_id: channel_id }), - Recipient.delete({ channel_id: channel_id }), ReadState.delete({ channel_id: channel_id }), + Message.delete({ channel_id: channel_id }), + Recipient.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); -- cgit 1.5.1 From 2d183f4c455f1eb549b30902a64c9eaa4eba8708 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:33:33 +0200 Subject: Update index.ts --- api/src/routes/channels/#channel_id/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index dcbf6d4e..fde75ec9 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, Message, Invite, ReadState, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -31,10 +31,6 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request await Channel.removeRecipientFromChannel(channel, req.user_id) } else { await Promise.all([ - Invite.delete({ channel_id: channel_id }), - ReadState.delete({ channel_id: channel_id }), - Message.delete({ channel_id: channel_id }), - Recipient.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); -- cgit 1.5.1 From 62f9b35185bacfe5cc0bfcf34c485fc72bea9f10 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 20:22:56 +0200 Subject: :bug: fix .delete -> add onDelete: "CASCADE" --- api/src/routes/guilds/#guild_id/delete.ts | 12 +---- util/src/entities/Application.ts | 4 +- util/src/entities/Attachment.ts | 4 +- util/src/entities/Ban.ts | 8 +++- util/src/entities/BaseClass.ts | 74 +++++++++++++++---------------- util/src/entities/Channel.ts | 10 ++--- util/src/entities/ConnectedAccount.ts | 4 +- util/src/entities/Emoji.ts | 4 +- util/src/entities/Guild.ts | 2 - util/src/entities/Invite.ts | 12 +++-- util/src/entities/Member.ts | 9 ++-- util/src/entities/Message.ts | 13 ++++-- util/src/entities/ReadState.ts | 8 +++- util/src/entities/Recipient.ts | 8 +++- util/src/entities/Relationship.ts | 8 +++- util/src/entities/Role.ts | 4 +- util/src/entities/Session.ts | 4 +- util/src/entities/Sticker.ts | 4 +- util/src/entities/Team.ts | 4 +- util/src/entities/TeamMember.ts | 8 +++- util/src/entities/User.ts | 10 ++++- util/src/entities/VoiceState.ts | 16 +++++-- util/src/entities/Webhook.ts | 20 ++++++--- 23 files changed, 153 insertions(+), 97 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index 7c3c5530..bd158c56 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -13,15 +13,8 @@ router.post("/", route({}), async (req: Request, res: Response) => { const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); - // do not put everything into promise all, because of "QueryFailedError: SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" - - await Message.delete({ guild_id }); // messages must be deleted before channel - await Promise.all([ - Role.delete({ guild_id }), - Channel.delete({ guild_id }), - Emoji.delete({ guild_id }), - Member.delete({ guild_id }), + Guild.delete({ id: guild_id }), // this will also delete all guild related data emitEvent({ event: "GUILD_DELETE", data: { @@ -31,9 +24,6 @@ router.post("/", route({}), async (req: Request, res: Response) => { } as GuildDeleteEvent) ]); - await Invite.delete({ guild_id }); // invite must be deleted after channel - await Guild.delete({ id: guild_id }); // guild must be deleted after everything else - return res.sendStatus(204); }); diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts index 2092cd4e..fab3d93f 100644 --- a/util/src/entities/Application.ts +++ b/util/src/entities/Application.ts @@ -41,7 +41,9 @@ export class Application extends BaseClass { verify_key: string; @JoinColumn({ name: "team_id" }) - @ManyToOne(() => Team) + @ManyToOne(() => Team, { + onDelete: "CASCADE", + }) team?: Team; @JoinColumn({ name: "guild_id" }) diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts index 82c5ecf5..7b4b17eb 100644 --- a/util/src/entities/Attachment.ts +++ b/util/src/entities/Attachment.ts @@ -31,7 +31,9 @@ export class Attachment extends BaseClass { message_id: string; @JoinColumn({ name: "message_id" }) - @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments) + @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { + onDelete: "CASCADE", + }) message: import("./Message").Message; @BeforeRemove() diff --git a/util/src/entities/Ban.ts b/util/src/entities/Ban.ts index e8a6d648..9504bd8e 100644 --- a/util/src/entities/Ban.ts +++ b/util/src/entities/Ban.ts @@ -10,7 +10,9 @@ export class Ban extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ nullable: true }) @@ -18,7 +20,9 @@ export class Ban extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 8a0b7a26..d18757f2 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -6,6 +6,7 @@ import { EntityMetadata, FindConditions, getConnection, + getManager, PrimaryColumn, RemoveOptions, } from "typeorm"; @@ -79,44 +80,41 @@ export class BaseClassWithoutId extends BaseEntity { return repository.decrement(conditions, propertyPath, value); } - static async delete(criteria: FindConditions, options?: RemoveOptions) { - if (!criteria) throw new Error("You need to specify delete criteria"); - - const repository = this.getRepository(); - const promises = repository.metadata.relations.map(async (x) => { - if (x.orphanedRowAction !== "delete") return; - if (typeof x.type === "string") return; - - const foreignKey = - x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || - x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity - if (!foreignKey) { - throw new Error( - `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` - ); - } - const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; - if (!id) throw new Error("id missing in criteria options"); - - if (x.relationType === "many-to-many") { - return getConnection() - .createQueryBuilder() - .relation(this, x.propertyName) - .of(id) - .remove({ [foreignKey.columnNames[0]]: id }); - } else if ( - x.relationType === "one-to-one" || - x.relationType === "many-to-one" || - x.relationType === "one-to-many" - ) { - return getConnection() - .getRepository(x.inverseEntityMetadata.target) - .delete({ [foreignKey.columnNames[0]]: id }); - } - }); - await Promise.all(promises); - return super.delete(criteria, options); - } + // static async delete(criteria: FindConditions, options?: RemoveOptions) { + // if (!criteria) throw new Error("You need to specify delete criteria"); + + // const repository = this.getRepository(); + // const promises = repository.metadata.relations.map(async (x) => { + // if (x.orphanedRowAction !== "delete") return; + + // const foreignKey = + // x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || + // x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity + // if (!foreignKey) { + // throw new Error( + // `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` + // ); + // } + // const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; + // if (!id) throw new Error("id missing in criteria options " + foreignKey.referencedColumnNames); + + // if (x.relationType === "many-to-many") { + // return getConnection() + // .createQueryBuilder() + // .relation(this, x.propertyName) + // .of(id) + // .remove({ [foreignKey.columnNames[0]]: id }); + // } else if ( + // x.relationType === "one-to-one" || + // x.relationType === "many-to-one" || + // x.relationType === "one-to-many" + // ) { + // return (x.inverseEntityMetadata.target as any).delete({ [foreignKey.columnNames[0]]: id }); + // } + // }); + // await Promise.all(promises); + // return super.delete(criteria, options); + // } } export class BaseClass extends BaseClassWithoutId { diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 74611eea..ece82bd0 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -45,7 +45,6 @@ export class Channel extends BaseClass { @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) recipients?: Recipient[]; @@ -57,7 +56,9 @@ export class Channel extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -110,35 +111,30 @@ export class Channel extends BaseClass { @OneToMany(() => Invite, (invite: Invite) => invite.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) invites?: Invite[]; @OneToMany(() => Message, (message: Message) => message.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) messages?: Message[]; @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) voice_states?: VoiceState[]; @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) read_states?: ReadState[]; @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) webhooks?: Webhook[]; diff --git a/util/src/entities/ConnectedAccount.ts b/util/src/entities/ConnectedAccount.ts index 59a8f423..b8aa2889 100644 --- a/util/src/entities/ConnectedAccount.ts +++ b/util/src/entities/ConnectedAccount.ts @@ -11,7 +11,9 @@ export class ConnectedAccount extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ select: false }) diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts index 181aff2c..a252d9f4 100644 --- a/util/src/entities/Emoji.ts +++ b/util/src/entities/Emoji.ts @@ -15,7 +15,9 @@ export class Emoji extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column() diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index d46d2161..e107937d 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -84,7 +84,6 @@ export class Guild extends BaseClass { @OneToMany(() => Ban, (ban: Ban) => ban.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) bans: Ban[]; @@ -147,7 +146,6 @@ export class Guild extends BaseClass { @OneToMany(() => Channel, (channel: Channel) => channel.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) channels: Channel[]; diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts index afad9c02..cb308823 100644 --- a/util/src/entities/Invite.ts +++ b/util/src/entities/Invite.ts @@ -34,7 +34,9 @@ export class Invite extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -42,7 +44,9 @@ export class Invite extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -58,7 +62,9 @@ export class Invite extends BaseClass { target_user_id: string; @JoinColumn({ name: "target_user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62 @Column({ nullable: true }) diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts index 66f5d9a1..feb9c069 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts @@ -39,7 +39,9 @@ export class Member extends BaseClassWithoutId { id: string; @JoinColumn({ name: "id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column() @@ -47,7 +49,9 @@ export class Member extends BaseClassWithoutId { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -55,7 +59,6 @@ export class Member extends BaseClassWithoutId { @JoinTable({ name: "member_roles", - joinColumn: { name: "index", referencedColumnName: "index" }, inverseJoinColumn: { name: "role_id", diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts index 0712f545..c4901693 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts @@ -54,7 +54,9 @@ export class Message extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -62,7 +64,9 @@ export class Message extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild?: Guild; @Column({ nullable: true }) @@ -70,7 +74,9 @@ export class Message extends BaseClass { author_id: string; @JoinColumn({ name: "author_id", referencedColumnName: "id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) author?: User; @Column({ nullable: true }) @@ -132,7 +138,6 @@ export class Message extends BaseClass { @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) attachments?: Attachment[]; diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts index 8dd05b21..68e867a0 100644 --- a/util/src/entities/ReadState.ts +++ b/util/src/entities/ReadState.ts @@ -15,7 +15,9 @@ export class ReadState extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -23,7 +25,9 @@ export class ReadState extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ nullable: true }) diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts index bb280588..a945f938 100644 --- a/util/src/entities/Recipient.ts +++ b/util/src/entities/Recipient.ts @@ -8,7 +8,9 @@ export class Recipient extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => require("./Channel").Channel) + @ManyToOne(() => require("./Channel").Channel, { + onDelete: "CASCADE", + }) channel: import("./Channel").Channel; @Column() @@ -16,7 +18,9 @@ export class Recipient extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => require("./User").User) + @ManyToOne(() => require("./User").User, { + onDelete: "CASCADE", + }) user: import("./User").User; @Column({ default: false }) diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts index 61b3ac82..e016b36b 100644 --- a/util/src/entities/Relationship.ts +++ b/util/src/entities/Relationship.ts @@ -17,7 +17,9 @@ export class Relationship extends BaseClass { from_id: string; @JoinColumn({ name: "from_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) from: User; @Column({}) @@ -25,7 +27,9 @@ export class Relationship extends BaseClass { to_id: string; @JoinColumn({ name: "to_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) to: User; @Column({ nullable: true }) diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts index 33c8d272..9fca99a5 100644 --- a/util/src/entities/Role.ts +++ b/util/src/entities/Role.ts @@ -10,7 +10,9 @@ export class Role extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column() diff --git a/util/src/entities/Session.ts b/util/src/entities/Session.ts index d42a8f98..7cc325f5 100644 --- a/util/src/entities/Session.ts +++ b/util/src/entities/Session.ts @@ -11,7 +11,9 @@ export class Session extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; //TODO check, should be 32 char long hex string diff --git a/util/src/entities/Sticker.ts b/util/src/entities/Sticker.ts index 7730a86a..ab224d1d 100644 --- a/util/src/entities/Sticker.ts +++ b/util/src/entities/Sticker.ts @@ -31,7 +31,9 @@ export class Sticker extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild?: Guild; @Column({ type: "simple-enum", enum: StickerType }) diff --git a/util/src/entities/Team.ts b/util/src/entities/Team.ts index beb8bf68..22140b7f 100644 --- a/util/src/entities/Team.ts +++ b/util/src/entities/Team.ts @@ -9,7 +9,9 @@ export class Team extends BaseClass { icon?: string; @JoinColumn({ name: "member_ids" }) - @OneToMany(() => TeamMember, (member: TeamMember) => member.team) + @OneToMany(() => TeamMember, (member: TeamMember) => member.team, { + orphanedRowAction: "delete", + }) members: TeamMember[]; @Column() diff --git a/util/src/entities/TeamMember.ts b/util/src/entities/TeamMember.ts index 6b184d08..bdfdccf0 100644 --- a/util/src/entities/TeamMember.ts +++ b/util/src/entities/TeamMember.ts @@ -20,7 +20,9 @@ export class TeamMember extends BaseClass { team_id: string; @JoinColumn({ name: "team_id" }) - @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members) + @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { + onDelete: "CASCADE", + }) team: import("./Team").Team; @Column({ nullable: true }) @@ -28,6 +30,8 @@ export class TeamMember extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; } diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index cef88777..4c86b2d8 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -127,11 +127,17 @@ export class User extends BaseClass { public_flags: number; @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from) + @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { + cascade: true, + orphanedRowAction: "delete", + }) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) - @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user) + @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { + cascade: true, + orphanedRowAction: "delete", + }) connected_accounts: ConnectedAccount[]; @Column({ type: "simple-json", select: false }) diff --git a/util/src/entities/VoiceState.ts b/util/src/entities/VoiceState.ts index 56eb244e..75748a01 100644 --- a/util/src/entities/VoiceState.ts +++ b/util/src/entities/VoiceState.ts @@ -13,7 +13,9 @@ export class VoiceState extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild?: Guild; @Column({ nullable: true }) @@ -21,7 +23,9 @@ export class VoiceState extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -29,11 +33,15 @@ export class VoiceState extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; // @JoinColumn([{ name: "user_id", referencedColumnName: "id" },{ name: "guild_id", referencedColumnName: "guild_id" }]) - // @ManyToOne(() => Member) + // @ManyToOne(() => Member, { + // onDelete: "CASCADE", + // }) //TODO find a way to make it work without breaking Guild.voice_states member: Member; diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts index 12ba0d08..8382435f 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts @@ -32,7 +32,9 @@ export class Webhook extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -40,7 +42,9 @@ export class Webhook extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -48,7 +52,9 @@ export class Webhook extends BaseClass { application_id: string; @JoinColumn({ name: "application_id" }) - @ManyToOne(() => Application) + @ManyToOne(() => Application, { + onDelete: "CASCADE", + }) application: Application; @Column({ nullable: true }) @@ -56,7 +62,9 @@ export class Webhook extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ nullable: true }) @@ -64,6 +72,8 @@ export class Webhook extends BaseClass { source_guild_id: string; @JoinColumn({ name: "source_guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) source_guild: Guild; } -- cgit 1.5.1 From 5b3ac6dadbfec8d2a0411858dfdfa715b7ab06ee Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:34:33 +0200 Subject: :bug: fix relationships --- api/assets/schemas.json | 254 +----------------------------- api/src/routes/users/@me/relationships.ts | 14 +- 2 files changed, 14 insertions(+), 254 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index da193b28..cc45ebb3 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -385,15 +385,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -707,15 +698,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -978,15 +960,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1228,15 +1201,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1481,15 +1445,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1743,15 +1698,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1998,15 +1944,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2248,15 +2185,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2510,15 +2438,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2785,15 +2704,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3099,15 +3009,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3349,15 +3250,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3599,15 +3491,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3861,15 +3744,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4118,15 +3992,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4371,15 +4236,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4624,15 +4480,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4873,15 +4720,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5142,15 +4980,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5418,15 +5247,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5672,15 +5492,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5928,15 +5739,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6184,15 +5986,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6461,15 +6254,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6478,13 +6262,16 @@ "type": "object", "properties": { "type": { - "$ref": "#/definitions/RelationshipType" + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, "additionalProperties": false, - "required": [ - "type" - ], "definitions": { "ChannelPermissionOverwriteType": { "enum": [ @@ -6711,15 +6498,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6965,15 +6743,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -7402,15 +7171,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 6ad873a6..567c734e 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -21,20 +21,20 @@ router.get("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] }); //TODO DTO - const related_users = user.relationships.map(r => { + const related_users = user.relationships.map((r) => { return { id: r.to.id, type: r.type, nickname: null, - user: r.to.toPublicUser(), - } - }) + user: r.to.toPublicUser() + }; + }); return res.json(related_users); }); export interface RelationshipPutSchema { - type: RelationshipType; + type?: RelationshipType; } router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => { @@ -42,7 +42,7 @@ router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request req, res, await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }), - req.body.type + req.body.type ?? RelationshipType.friends ); }); @@ -59,7 +59,7 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, relations: ["relationships", "relationships.to"], select: userProjection, where: { - discriminator: String(req.body.discriminator).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes + discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes username: req.body.username } }), -- cgit 1.5.1 From 8d611abe45e971cc0c873deaaa63d4a7c59df98e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:04 +0200 Subject: :bug: fix errror handler --- api/src/middlewares/ErrorHandler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index d288f3fb..96e703ce 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -3,6 +3,7 @@ import { HTTPError } from "lambert-server"; import { EntityNotFoundError } from "typeorm"; import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; +const EntityNotFoundErrorRegex = /"(\w+)"/; export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) return next(); @@ -18,9 +19,9 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne code = error.code; message = error.message; httpcode = error.httpStatus; - } else if (error instanceof EntityNotFoundError) { - message = `${(error as any).stringifyTarget || "Item"} could not be found`; - code = 404; + } else if (error.name === "EntityNotFoundError") { + message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; + code = httpcode = 404; } else if (error instanceof FieldError) { code = Number(error.code); message = error.message; -- cgit 1.5.1 From cc33e87a1451d1b327fa8945394806cc0a7a0e91 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:32 +0200 Subject: :sparkles: add option to disable all rate limits --- api/src/middlewares/BodyParser.ts | 2 ++ api/src/middlewares/RateLimit.ts | 3 ++- util/src/entities/Config.ts | 2 ++ util/src/entities/User.ts | 4 +--- util/src/util/Config.ts | 2 +- util/src/util/Database.ts | 3 ++- 6 files changed, 10 insertions(+), 6 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/BodyParser.ts b/api/src/middlewares/BodyParser.ts index b0ff699d..4cb376bc 100644 --- a/api/src/middlewares/BodyParser.ts +++ b/api/src/middlewares/BodyParser.ts @@ -6,6 +6,8 @@ export function BodyParser(opts?: OptionsJson) { const jsonParser = bodyParser.json(opts); return (req: Request, res: Response, next: NextFunction) => { + if (!req.headers["content-type"]) req.headers["content-type"] = "application/json"; + jsonParser(req, res, (err) => { if (err) { // TODO: different errors for body parser (request size limit, wrong body type, invalid body, ...) diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index d1fd072f..1a38cfcf 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -107,7 +107,8 @@ export default function rateLimit(opts: { } export async function initRateLimits(app: Router) { - const { routes, global, ip, error } = Config.get().limits.rate; + const { routes, global, ip, error, disabled } = Config.get().limits.rate; + if (disabled) return; await listenEvent(EventRateLimit, (event) => { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index f969b6bb..a460b437 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -77,6 +77,7 @@ export interface ConfigValue { maxWebhooks: number; }; rate: { + disabled: boolean; ip: Omit; global: RateLimitOptions; error: RateLimitOptions; @@ -188,6 +189,7 @@ export const DefaultConfigOptions: ConfigValue = { maxWebhooks: 10, }, rate: { + disabled: true, ip: { count: 500, window: 5, diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 4c86b2d8..b5c2c308 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -161,15 +161,13 @@ export class User extends BaseClass { } static async getPublicUser(user_id: string, opts?: FindOneOptions) { - const user = await User.findOne( + return await User.findOneOrFail( { id: user_id }, { ...opts, select: [...PublicUserProjection, ...(opts?.select || [])], } ); - if (!user) throw new HTTPError("User not found", 404); - return user; } } diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts index 1ec71ad0..c87d598e 100644 --- a/util/src/util/Config.ts +++ b/util/src/util/Config.ts @@ -14,7 +14,7 @@ export const Config = { get: function get() { return config.value as ConfigValue; }, - set: function set(val: any) { + set: function set(val: Partial) { if (!config) return; config.value = val.merge(config?.value || {}); return config.save(); diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index c22d8abd..0c3d7cef 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -1,3 +1,4 @@ +import path from "path"; import "reflect-metadata"; import { Connection, createConnection, ValueTransformer } from "typeorm"; import * as Models from "../entities"; @@ -15,7 +16,7 @@ export function initDatabase() { // @ts-ignore promise = createConnection({ type: "sqlite", - database: "database.db", + database: path.join(process.cwd(), "database.db"), // type: "postgres", // url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord", // -- cgit 1.5.1 From eb2f447d962c17c1e13ba69c4203cd98091847d3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:37 +0200 Subject: :bug: fix unittests --- api/jest/globalSetup.js | 7 +++- api/jest/setup.js | 4 +-- api/src/util/route.ts | 4 +-- api/tests/routes.test.ts | 88 +++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 82 insertions(+), 21 deletions(-) (limited to 'api/src') diff --git a/api/jest/globalSetup.js b/api/jest/globalSetup.js index 98e70fb9..520aa0e2 100644 --- a/api/jest/globalSetup.js +++ b/api/jest/globalSetup.js @@ -1,3 +1,4 @@ +const { Config, initDatabase } = require("@fosscord/util"); const fs = require("fs"); const path = require("path"); const { FosscordServer } = require("../dist/Server"); @@ -5,8 +6,12 @@ const Server = new FosscordServer({ port: 3001 }); global.server = Server; module.exports = async () => { try { - fs.unlinkSync(path.join(__dirname, "..", "database.db")); + fs.unlinkSync(path.join(process.cwd(), "database.db")); } catch {} + + await initDatabase(); + await Config.init(); + Config.get().limits.rate.disabled = true; return await Server.start(); }; diff --git a/api/jest/setup.js b/api/jest/setup.js index bd535866..abc485ae 100644 --- a/api/jest/setup.js +++ b/api/jest/setup.js @@ -1,2 +1,2 @@ -// jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); -// jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); +jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); +jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 5b06a2b5..e7c7ed1c 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { DiscordApiErrors, EVENT, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -38,7 +38,7 @@ export interface RouteOptions { response?: RouteResponse; body?: any; path?: string; - event?: EventData | EventData[]; + event?: EVENT | EVENT[]; headers?: Record; }; } diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index 4cb7e6bc..0473c579 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -2,12 +2,12 @@ // TODO: check every route with different database engine import getRouteDescriptions from "../jest/getRouteDescriptions"; -import supertest, { Response } from "supertest"; import { join } from "path"; import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; -const request = supertest("http://localhost:3001/api"); +import fetch from "node-fetch"; +import { User } from "@fosscord/util"; const SchemaPath = join(__dirname, "..", "assets", "responses.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); @@ -18,34 +18,90 @@ export const ajv = new Ajv({ schemas, messages: true, strict: true, - strictRequired: true + strictRequired: true, + coerceTypes: true }); addFormats(ajv); +var token: string; +var user: User; +beforeAll(async (done) => { + try { + const response = await fetch("http://localhost:3001/api/auth/register", { + method: "POST", + body: JSON.stringify({ + fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw", + email: "test@example.com", + username: "tester", + password: "wtp9gep9gw", + invite: null, + consent: true, + date_of_birth: "2000-01-01", + gift_code_sku_id: null, + captcha_key: null + }), + headers: { + "content-type": "application/json" + } + }); + const json = await response.json(); + token = json.token; + user = await ( + await fetch(`http://localhost:3001/api/users/@me`, { + headers: { authorization: token } + }) + ).json(); + + done(); + } catch (error) { + done(error); + } +}); + describe("Automatic unit tests with route description middleware", () => { const routes = getRouteDescriptions(); routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); - test(path, (done) => { - if (!route.example) { + test(path, async (done) => { + if (!route.test) { console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); } - const urlPath = path || route.example?.path; - const validate = ajv.getSchema(route.response.body); - if (!validate) return done(new Error(`Response schema ${route.response.body} not found`)); - - request[method](urlPath) - .expect(route.response.status) - .expect((err: any, res: Response) => { - if (err) return done(err); - const valid = validate(res.body); - if (!valid) return done(validate.errors); + const urlPath = path.replace(":id", user.id) || route.test?.path; + var validate: any; + if (route.test.body) { + validate = ajv.getSchema(route.test.body); + if (!validate) return done(new Error(`Response schema ${route.test.body} not found`)); + } + + var body = ""; - return done(); + try { + const response = await fetch(`http://localhost:3001/api${urlPath}`, { + method: method.toUpperCase(), + body: JSON.stringify(route.test.body), + headers: { ...route.test.headers, authorization: token } }); + + body = await response.text(); + + expect(response.status, body).toBe(route.test.response.status || 200); + + // TODO: check headers + // TODO: expect event + + if (validate) { + body = JSON.parse(body); + const valid = validate(body); + if (!valid) return done(validate.errors); + } + } catch (error) { + return done(error); + } + + return done(); }); }); }); -- cgit 1.5.1 From 2a094c603a35c7174022ec2d2ffa42fa28508e3b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 21 Sep 2021 22:52:30 +0200 Subject: :sparkles: generate openapi documentation --- api/assets/openapi.json | 5978 +++++++++++++++++--- api/assets/responses.json | 90 - api/assets/schemas.json | 2013 ++++++- api/jest/getRouteDescriptions.js | 66 + api/jest/getRouteDescriptions.ts | 58 - api/scripts/generate_body_schema.js | 70 + api/scripts/generate_body_schema.ts | 60 - api/scripts/generate_openapi_schema.js | 127 + api/scripts/generate_openapi_schema.ts | 92 - api/scripts/generate_test_schema.ts | 68 - api/src/routes/auth/login.ts | 23 +- api/src/routes/auth/register.ts | 24 +- api/src/routes/channels/#channel_id/recipients.ts | 5 +- .../members/#member_id/roles/#role_id/index.ts | 4 +- api/src/routes/sticker-packs/#id/index.ts | 5 +- api/src/routes/sticker-packs/index.ts | 5 +- api/src/util/String.ts | 2 - api/tests/routes.test.ts | 4 +- util/src/util/Email.ts | 20 + util/src/util/Token.ts | 43 + util/src/util/checkToken.ts | 23 - util/src/util/index.ts | 3 +- 22 files changed, 7575 insertions(+), 1208 deletions(-) delete mode 100644 api/assets/responses.json create mode 100644 api/jest/getRouteDescriptions.js delete mode 100644 api/jest/getRouteDescriptions.ts create mode 100644 api/scripts/generate_body_schema.js delete mode 100644 api/scripts/generate_body_schema.ts create mode 100644 api/scripts/generate_openapi_schema.js delete mode 100644 api/scripts/generate_openapi_schema.ts delete mode 100644 api/scripts/generate_test_schema.ts create mode 100644 util/src/util/Email.ts create mode 100644 util/src/util/Token.ts delete mode 100644 util/src/util/checkToken.ts (limited to 'api/src') diff --git a/api/assets/openapi.json b/api/assets/openapi.json index fa527911..caf572b9 100644 --- a/api/assets/openapi.json +++ b/api/assets/openapi.json @@ -4,7 +4,12 @@ { "url": "https://api.fosscord.com/v{version}", "description": "Official fosscord instance", - "variables": { "version": { "description": "", "default": "9", "enum": ["8", "9"] } } + "variables": { + "version": { + "default": "9", + "enum": ["8", "9"] + } + } } ], "info": { @@ -12,95 +17,2866 @@ "version": "1.0.0", "title": "Fosscord HTTP API Routes", "termsOfService": "", - "contact": { "name": "Fosscord" }, - "license": { "name": "AGPLV3", "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" } + "contact": { + "name": "Fosscord" + }, + "license": { + "name": "AGPLV3", + "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" + } }, - "tags": [], + "tags": [ + { + "name": "voice" + }, + { + "name": "users" + }, + { + "name": "store" + }, + { + "name": "sticker-packs" + }, + { + "name": "science" + }, + { + "name": "ping" + }, + { + "name": "outbound-promotions" + }, + { + "name": "invites" + }, + { + "name": "guilds" + }, + { + "name": "gateway" + }, + { + "name": "experiments" + }, + { + "name": "discoverable-guilds" + }, + { + "name": "channels" + }, + { + "name": "auth" + }, + { + "name": "applications" + } + ], "paths": { "/users/{id}": { "get": { "summary": "", - "description": "", - "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "user id" }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user id" + } + ], "operationId": "", "responses": { "200": { "description": "User found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserPublic" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPublic" + } + } + } }, "404": { "description": "User not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "Token": [] + } + ] + } + }, + "/users/@me": { + "get": { + "summary": "", + "parameters": [], + "operationId": "", + "responses": { + "200": { + "description": "Authenticated user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPublic" + } + } + } + } + }, + "security": [ + { + "Token": [] + } + ] + } + }, + "/voice/regions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["voice"] + } + }, + "/users/@me/settings/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSettingsSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/relationships/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users", "relationships"] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipPostSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/relationships/{id}": { + "put": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipPutSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/users/@me/library/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/guilds/{id}": { + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/users/@me/disable/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/devices/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/delete/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/connections/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/channels/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DmChannelCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/billing/subscriptions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/billing/country-code/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/applications/{app_id}/entitlements/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "app_id" + } + ], + "tags": ["users"] + } + }, + "/users/@me/affinities/users/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/affinities/guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/{id}/profile/": { + "get": { + "description": "", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserProfileResponse" + } + } + }, + "description": "" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/users/{id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/store/skus/skus/{id}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["store"] + } + }, + "/store/applications/applications/{id}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["store"] + } + }, + "/sticker-packs/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["sticker", "sticker-packs"] + } + }, + "/sticker-packs/{id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["sticker", "sticker-packs"] + } + }, + "/science/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["science"] + } + }, + "/ping/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["ping"] + } + }, + "/outbound-promotions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["outbound", "outbound-promotions"] + } + }, + "/invites/{code}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["invites"] + }, + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["invites"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["invites"] + } + }, + "/guilds/templates/{code}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildTemplateCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/widget.png/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/widget.json/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/widget/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WidgetModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/welcome_screen/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildUpdateWelcomeScreenSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/voice-states/{user_id}/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceStateUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/vanity-url/": { + "get": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VanityUrlSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/templates/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/templates/{code}": { + "delete": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + }, + "put": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/roles/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RolePositionUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/roles/{role_id}": { + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/regions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/members/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/members/{member_id}/nick/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberNickChangeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/members/{member_id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberChangeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + }, + "put": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + }, + "delete": { + "description": "##### Requires the ``KICK_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/invites/": { + "get": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/delete/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/channels/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelReorderSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/bans/": { + "get": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/bans/{user}": { + "get": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/bans/{user_id}": { + "put": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BanCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["guilds"] + }, + "delete": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["guilds"] + } + }, + "/gateway/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["gateway"] + } + }, + "/gateway/bot": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["gateway"] + } + }, + "/experiments/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["experiments"] + } + }, + "/discoverable-guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["discoverable", "discoverable-guilds"] + } + }, + "/channels/{channel_id}/webhooks/": { + "post": { + "description": "##### Requires the ``MANAGE_WEBHOOKS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/typing/": { + "post": { + "description": "##### Requires the ``SEND_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/recipients/{user_id}": { + "put": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/pins/{message_id}": { + "put": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/pins/": { + "get": { + "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/permissions/{overwrite_id}": { + "put": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "overwrite_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "overwrite_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "overwrite_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "overwrite_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/bulk-delete/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkDeleteSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/": { + "delete": { + "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}": { + "delete": { + "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + } + ], + "tags": ["channels"] + }, + "get": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{user_id}": { + "put": { + "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/": { + "patch": { + "description": "##### Requires the ``SEND_MESSAGES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/ack/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageAcknowledgeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/invites/": { + "post": { + "description": "##### Requires the ``CREATE_INSTANT_INVITE`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + }, + "get": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/": { + "get": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/auth/register/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["auth"] + } + }, + "/applications/detectable/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["applications"] + } + }, + "/guilds/{guild_id}/members/{member_id}/roles/{role_id}/": { + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + }, + "put": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" } }, - "security": [{ "Token": [] }] + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] } }, - "/users/@me": { - "get": { - "summary": "", + "/auth/login/": { + "post": { "description": "", - "parameters": [], - "operationId": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginSchema" + } + } + } + }, "responses": { - "200": { - "description": "Authenticated user", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserPublic" } } } + "default": { + "description": "not documented" } }, - "security": [{ "Token": [] }] + "tags": ["auth"] } } }, - "externalDocs": { "description": "", "url": "http://docs.fosscord.com/" }, + "externalDocs": { + "url": "http://docs.fosscord.com/" + }, "components": { "schemas": { "Error": { "type": "object", - "properties": { "code": { "type": "integer" }, "message": { "type": "string" } }, + "properties": { + "code": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, "required": ["code", "message"] }, "RateLimit": { "type": "object", - "properties": { "retry_after": { "type": "integer" }, "message": { "type": "string" }, "global": { "type": "boolean" } }, + "properties": { + "retry_after": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "global": { + "type": "boolean" + } + }, "required": ["code", "message", "globa"] }, "User": { "type": "object", "properties": { - "username": { "type": "string" }, - "discriminator": { "type": "string" }, - "avatar": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "phone": { "type": "string" }, - "desktop": { "type": "boolean" }, - "mobile": { "type": "boolean" }, - "premium": { "type": "boolean" }, - "premium_type": { "type": "integer" }, - "bot": { "type": "boolean" }, - "bio": { "type": "string" }, - "system": { "type": "boolean" }, - "nsfw_allowed": { "type": "boolean" }, - "mfa_enabled": { "type": "boolean" }, - "created_at": { "type": "string", "format": "date-time" }, - "verified": { "type": "boolean" }, - "disabled": { "type": "boolean" }, - "deleted": { "type": "boolean" }, - "email": { "type": "string" }, - "flags": { "type": "string" }, - "public_flags": { "type": "string" }, - "relationships": { "type": "array", "items": { "$ref": "#/components/schemas/Relationship" } }, - "connected_accounts": { "type": "array", "items": { "$ref": "#/components/schemas/ConnectedAccount" } }, + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "desktop": { + "type": "boolean" + }, + "mobile": { + "type": "boolean" + }, + "premium": { + "type": "boolean" + }, + "premium_type": { + "type": "integer" + }, + "bot": { + "type": "boolean" + }, + "bio": { + "type": "string" + }, + "system": { + "type": "boolean" + }, + "nsfw_allowed": { + "type": "boolean" + }, + "mfa_enabled": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "verified": { + "type": "boolean" + }, + "disabled": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "relationships": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relationship" + } + }, + "connected_accounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectedAccount" + } + }, "data": { "type": "object", - "properties": { "valid_tokens_since": { "type": "string", "format": "date-time" }, "hash": { "type": "string" } }, + "properties": { + "valid_tokens_since": { + "type": "string", + "format": "date-time" + }, + "hash": { + "type": "string" + } + }, "additionalProperties": false, "required": ["valid_tokens_since"] }, - "fingerprints": { "type": "array", "items": { "type": "string" } }, - "settings": { "$ref": "#/components/schemas/UserSettings" }, - "id": { "type": "string" } + "fingerprints": { + "type": "array", + "items": { + "type": "string" + } + }, + "settings": { + "$ref": "#/components/schemas/UserSettings" + }, + "id": { + "type": "string" + } }, "required": [ "bio", @@ -131,29 +2907,64 @@ "Relationship": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "nickname": { "type": "string" }, - "type": { "$ref": "#/components/schemas/RelationshipType" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "nickname": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/RelationshipType" + }, + "id": { + "type": "string" + } }, "required": ["id", "type", "user", "user_id"] }, - "RelationshipType": { "enum": [1, 2, 3, 4], "type": "number" }, + "RelationshipType": { + "enum": [1, 2, 3, 4], + "type": "number" + }, "ConnectedAccount": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "access_token": { "type": "string" }, - "friend_sync": { "type": "boolean" }, - "name": { "type": "string" }, - "revoked": { "type": "boolean" }, - "show_activity": { "type": "boolean" }, - "type": { "type": "string" }, - "verifie": { "type": "boolean" }, - "visibility": { "type": "integer" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "access_token": { + "type": "string" + }, + "friend_sync": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "revoked": { + "type": "boolean" + }, + "show_activity": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + }, + "visibility": { + "type": "integer" + }, + "id": { + "type": "string" + } }, "required": [ "access_token", @@ -172,64 +2983,151 @@ "UserSettings": { "type": "object", "properties": { - "afk_timeout": { "type": "integer" }, - "allow_accessibility_detection": { "type": "boolean" }, - "animate_emoji": { "type": "boolean" }, - "animate_stickers": { "type": "integer" }, - "contact_sync_enabled": { "type": "boolean" }, - "convert_emoticons": { "type": "boolean" }, + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, "custom_status": { "type": "object", "properties": { - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" }, - "expires_at": { "type": "integer" }, - "text": { "type": "string" } + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } }, "additionalProperties": false }, - "default_guilds_restricted": { "type": "boolean" }, - "detect_platform_accounts": { "type": "boolean" }, - "developer_mode": { "type": "boolean" }, - "disable_games_tab": { "type": "boolean" }, - "enable_tts_command": { "type": "boolean" }, - "explicit_content_filter": { "type": "integer" }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, "friend_source_flags": { "type": "object", - "properties": { "all": { "type": "boolean" } }, + "properties": { + "all": { + "type": "boolean" + } + }, "additionalProperties": false, "required": ["all"] }, - "gateway_connected": { "type": "boolean" }, - "gif_auto_play": { "type": "boolean" }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, "guild_folders": { "type": "array", "items": { "type": "object", "properties": { - "color": { "type": "integer" }, - "guild_ids": { "type": "array", "items": { "type": "string" } }, - "id": { "type": "integer" }, - "name": { "type": "string" } + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } }, "additionalProperties": false, "required": ["color", "guild_ids", "id", "name"] } }, - "guild_positions": { "type": "array", "items": { "type": "string" } }, - "inline_attachment_media": { "type": "boolean" }, - "inline_embed_media": { "type": "boolean" }, - "locale": { "type": "string" }, - "message_display_compact": { "type": "boolean" }, - "native_phone_integration_enabled": { "type": "boolean" }, - "render_embeds": { "type": "boolean" }, - "render_reactions": { "type": "boolean" }, - "restricted_guilds": { "type": "array", "items": { "type": "string" } }, - "show_current_game": { "type": "boolean" }, - "status": { "enum": ["dnd", "idle", "offline", "online"], "type": "string" }, - "stream_notifications_enabled": { "type": "boolean" }, - "theme": { "enum": ["dark", "white"], "type": "string" }, - "timezone_offset": { "type": "integer" } + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": ["dnd", "idle", "offline", "online"], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": ["dark", "white"], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } }, "required": [ "afk_timeout", @@ -268,93 +3166,264 @@ "Team": { "type": "object", "properties": { - "icon": { "type": "string" }, - "members": { "type": "array", "items": { "$ref": "#/components/schemas/TeamMember" } }, - "name": { "type": "string" }, - "owner_user_id": { "type": "string" }, - "owner_user": { "$ref": "#/components/schemas/User" }, - "id": { "type": "string" } + "icon": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamMember" + } + }, + "name": { + "type": "string" + }, + "owner_user_id": { + "type": "string" + }, + "owner_user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } }, "required": ["id", "members", "name", "owner_user", "owner_user_id"] }, "TeamMember": { "type": "object", "properties": { - "membership_state": { "$ref": "#/components/schemas/TeamMemberState" }, - "permissions": { "type": "array", "items": { "type": "string" } }, - "team_id": { "type": "string" }, - "team": { "$ref": "#/components/schemas/Team" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "id": { "type": "string" } + "membership_state": { + "$ref": "#/components/schemas/TeamMemberState" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "team_id": { + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/Team" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } }, "required": ["id", "membership_state", "permissions", "team", "team_id", "user", "user_id"] }, - "TeamMemberState": { "enum": [1, 2], "type": "number" }, + "TeamMemberState": { + "enum": [1, 2], + "type": "number" + }, "Guild": { "type": "object", "properties": { - "afk_channel_id": { "type": "string" }, - "afk_channel": { "$ref": "#/components/schemas/Channel" }, - "afk_timeout": { "type": "integer" }, - "bans": { "type": "array", "items": { "$ref": "#/components/schemas/Ban" } }, - "banner": { "type": "string" }, - "default_message_notifications": { "type": "integer" }, - "description": { "type": "string" }, - "discovery_splash": { "type": "string" }, - "explicit_content_filter": { "type": "integer" }, - "features": { "type": "array", "items": { "type": "string" } }, - "icon": { "type": "string" }, - "large": { "type": "boolean" }, - "max_members": { "type": "integer" }, - "max_presences": { "type": "integer" }, - "max_video_channel_users": { "type": "integer" }, - "member_count": { "type": "integer" }, - "presence_count": { "type": "integer" }, - "members": { "type": "array", "items": { "$ref": "#/components/schemas/Member" } }, - "roles": { "type": "array", "items": { "$ref": "#/components/schemas/Role" } }, - "channels": { "type": "array", "items": { "$ref": "#/components/schemas/Channel" } }, - "template_id": { "type": "string" }, - "template": { "$ref": "#/components/schemas/Template" }, - "emojis": { "type": "array", "items": { "$ref": "#/components/schemas/Emoji" } }, - "stickers": { "type": "array", "items": { "$ref": "#/components/schemas/Sticker" } }, - "invites": { "type": "array", "items": { "$ref": "#/components/schemas/Invite" } }, - "voice_states": { "type": "array", "items": { "$ref": "#/components/schemas/VoiceState" } }, - "webhooks": { "type": "array", "items": { "$ref": "#/components/schemas/Webhook" } }, - "mfa_level": { "type": "integer" }, - "name": { "type": "string" }, - "owner_id": { "type": "string" }, - "owner": { "$ref": "#/components/schemas/User" }, - "preferred_locale": { "type": "string" }, - "premium_subscription_count": { "type": "integer" }, - "premium_tier": { "type": "integer" }, - "public_updates_channel_id": { "type": "string" }, - "public_updates_channel": { "$ref": "#/components/schemas/Channel" }, - "rules_channel_id": { "type": "string" }, - "rules_channel": { "type": "string" }, - "region": { "type": "string" }, - "splash": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "system_channel": { "$ref": "#/components/schemas/Channel" }, - "system_channel_flags": { "type": "integer" }, - "unavailable": { "type": "boolean" }, - "vanity_url_code": { "type": "string" }, - "vanity_url": { "$ref": "#/components/schemas/Invite" }, - "verification_level": { "type": "integer" }, + "afk_channel_id": { + "type": "string" + }, + "afk_channel": { + "$ref": "#/components/schemas/Channel" + }, + "afk_timeout": { + "type": "integer" + }, + "bans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Ban" + } + }, + "banner": { + "type": "string" + }, + "default_message_notifications": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "discovery_splash": { + "type": "string" + }, + "explicit_content_filter": { + "type": "integer" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "icon": { + "type": "string" + }, + "large": { + "type": "boolean" + }, + "max_members": { + "type": "integer" + }, + "max_presences": { + "type": "integer" + }, + "max_video_channel_users": { + "type": "integer" + }, + "member_count": { + "type": "integer" + }, + "presence_count": { + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Member" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Channel" + } + }, + "template_id": { + "type": "string" + }, + "template": { + "$ref": "#/components/schemas/Template" + }, + "emojis": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Emoji" + } + }, + "stickers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sticker" + } + }, + "invites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invite" + } + }, + "voice_states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VoiceState" + } + }, + "webhooks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Webhook" + } + }, + "mfa_level": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "preferred_locale": { + "type": "string" + }, + "premium_subscription_count": { + "type": "integer" + }, + "premium_tier": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "public_updates_channel": { + "$ref": "#/components/schemas/Channel" + }, + "rules_channel_id": { + "type": "string" + }, + "rules_channel": { + "type": "string" + }, + "region": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "system_channel": { + "$ref": "#/components/schemas/Channel" + }, + "system_channel_flags": { + "type": "integer" + }, + "unavailable": { + "type": "boolean" + }, + "vanity_url_code": { + "type": "string" + }, + "vanity_url": { + "$ref": "#/components/schemas/Invite" + }, + "verification_level": { + "type": "integer" + }, "welcome_screen": { "type": "object", "properties": { - "enabled": { "type": "boolean" }, - "description": { "type": "string" }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + }, "welcome_channels": { "type": "array", "items": { "type": "object", "properties": { - "description": { "type": "string" }, - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" }, - "channel_id": { "type": "string" } + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "channel_id": { + "type": "string" + } }, "additionalProperties": false, "required": ["channel_id", "description", "emoji_name"] @@ -364,10 +3433,18 @@ "additionalProperties": false, "required": ["description", "enabled", "welcome_channels"] }, - "widget_channel_id": { "type": "string" }, - "widget_channel": { "$ref": "#/components/schemas/Channel" }, - "widget_enabled": { "type": "boolean" }, - "id": { "type": "string" } + "widget_channel_id": { + "type": "string" + }, + "widget_channel": { + "$ref": "#/components/schemas/Channel" + }, + "widget_enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": [ "bans", @@ -393,29 +3470,82 @@ "Channel": { "type": "object", "properties": { - "created_at": { "type": "string", "format": "date-time" }, - "name": { "type": "string" }, - "type": { "$ref": "#/components/schemas/ChannelType" }, - "recipients": { "type": "array", "items": { "$ref": "#/components/schemas/Recipient" } }, - "last_message_id": { "type": "string" }, - "last_message": { "$ref": "#/components/schemas/Message" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "parent_id": { "type": "string" }, - "parent": { "$ref": "#/components/schemas/Channel" }, - "owner_id": { "type": "string" }, - "owner": { "$ref": "#/components/schemas/User" }, - "last_pin_timestamp": { "type": "integer" }, - "default_auto_archive_duration": { "type": "integer" }, - "position": { "type": "integer" }, - "permission_overwrites": { "type": "array", "items": { "$ref": "#/components/schemas/ChannelPermissionOverwrite" } }, - "video_quality_mode": { "type": "integer" }, - "bitrate": { "type": "integer" }, - "user_limit": { "type": "integer" }, - "nsfw": { "type": "boolean" }, - "rate_limit_per_user": { "type": "integer" }, - "topic": { "type": "string" }, - "id": { "type": "string" } + "created_at": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelType" + }, + "recipients": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recipient" + } + }, + "last_message_id": { + "type": "string" + }, + "last_message": { + "$ref": "#/components/schemas/Message" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "parent_id": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/Channel" + }, + "owner_id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "last_pin_timestamp": { + "type": "integer" + }, + "default_auto_archive_duration": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelPermissionOverwrite" + } + }, + "video_quality_mode": { + "type": "integer" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "nsfw": { + "type": "boolean" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": [ "created_at", @@ -431,78 +3561,198 @@ "type" ] }, - "ChannelType": { "enum": [0, 1, 2, 3, 4, 5, 6], "type": "number" }, + "ChannelType": { + "enum": [0, 1, 2, 3, 4, 5, 6], + "type": "number" + }, "Recipient": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "user": { "$ref": "#/components/schemas/User" }, - "id": { "type": "string" } + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } }, "required": ["channel", "channel_id", "id", "user"] }, "Message": { "type": "object", "properties": { - "id": { "type": "string" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "author_id": { "type": "string" }, - "author": { "$ref": "#/components/schemas/User" }, - "member_id": { "type": "string" }, - "member": { "$ref": "#/components/schemas/Member" }, - "webhook_id": { "type": "string" }, - "webhook": { "$ref": "#/components/schemas/Webhook" }, - "application_id": { "type": "string" }, - "application": { "$ref": "#/components/schemas/Application" }, - "content": { "type": "string" }, - "timestamp": { "type": "string", "format": "date-time" }, - "edited_timestamp": { "type": "string", "format": "date-time" }, - "tts": { "type": "boolean" }, - "mention_everyone": { "type": "boolean" }, - "mentions": { "type": "array", "items": { "$ref": "#/components/schemas/User" } }, - "mention_roles": { "type": "array", "items": { "$ref": "#/components/schemas/Role" } }, - "mention_channels": { "type": "array", "items": { "$ref": "#/components/schemas/Channel" } }, - "sticker_items": { "type": "array", "items": { "$ref": "#/components/schemas/Sticker" } }, - "attachments": { "type": "array", "items": { "$ref": "#/components/schemas/Attachment" } }, - "embeds": { "type": "array", "items": { "$ref": "#/components/schemas/Embed" } }, - "reactions": { "type": "array", "items": { "$ref": "#/components/schemas/Reaction" } }, - "nonce": { "type": "string" }, - "pinned": { "type": "boolean" }, - "type": { "$ref": "#/components/schemas/MessageType" }, + "id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "author_id": { + "type": "string" + }, + "author": { + "$ref": "#/components/schemas/User" + }, + "member_id": { + "type": "string" + }, + "member": { + "$ref": "#/components/schemas/Member" + }, + "webhook_id": { + "type": "string" + }, + "webhook": { + "$ref": "#/components/schemas/Webhook" + }, + "application_id": { + "type": "string" + }, + "application": { + "$ref": "#/components/schemas/Application" + }, + "content": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "edited_timestamp": { + "type": "string", + "format": "date-time" + }, + "tts": { + "type": "boolean" + }, + "mention_everyone": { + "type": "boolean" + }, + "mentions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "mention_roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "mention_channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Channel" + } + }, + "sticker_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sticker" + } + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attachment" + } + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embed" + } + }, + "reactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Reaction" + } + }, + "nonce": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "type": { + "$ref": "#/components/schemas/MessageType" + }, "activity": { "type": "object", - "properties": { "type": { "type": "integer" }, "party_id": { "type": "string" } }, + "properties": { + "type": { + "type": "integer" + }, + "party_id": { + "type": "string" + } + }, "additionalProperties": false, "required": ["party_id", "type"] }, - "flags": { "type": "string" }, + "flags": { + "type": "string" + }, "message_reference": { "type": "object", "properties": { - "message_id": { "type": "string" }, - "channel_id": { "type": "string" }, - "guild_id": { "type": "string" } + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + } }, "additionalProperties": false, "required": ["message_id"] }, - "referenced_message": { "$ref": "#/components/schemas/Message" }, + "referenced_message": { + "$ref": "#/components/schemas/Message" + }, "interaction": { "type": "object", "properties": { - "id": { "type": "string" }, - "type": { "$ref": "#/components/schemas/InteractionType" }, - "name": { "type": "string" }, - "user_id": { "type": "string" } + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/InteractionType" + }, + "name": { + "type": "string" + }, + "user_id": { + "type": "string" + } }, "additionalProperties": false, "required": ["id", "name", "type", "user_id"] }, - "components": { "type": "array", "items": { "$ref": "#/components/schemas/MessageComponent" } } + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageComponent" + } + } }, "required": [ "application_id", @@ -524,44 +3774,100 @@ "Member": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "nick": { "type": "string" }, - "roles": { "type": "array", "items": { "$ref": "#/components/schemas/Role" } }, - "joined_at": { "type": "string", "format": "date-time" }, - "premium_since": { "type": "integer" }, - "deaf": { "type": "boolean" }, - "mute": { "type": "boolean" }, - "pending": { "type": "boolean" }, - "settings": { "$ref": "#/components/schemas/UserGuildSettings" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "nick": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "joined_at": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "integer" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "pending": { + "type": "boolean" + }, + "settings": { + "$ref": "#/components/schemas/UserGuildSettings" + }, + "id": { + "type": "string" + } }, "required": ["deaf", "guild", "guild_id", "id", "joined_at", "mute", "pending", "roles", "settings", "user", "user_id"] }, "Role": { "type": "object", "properties": { - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "color": { "type": "integer" }, - "hoist": { "type": "boolean" }, - "managed": { "type": "boolean" }, - "mentionable": { "type": "boolean" }, - "name": { "type": "string" }, - "permissions": { "type": "string" }, - "position": { "type": "integer" }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "managed": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "position": { + "type": "integer" + }, "tags": { "type": "object", "properties": { - "bot_id": { "type": "string" }, - "integration_id": { "type": "string" }, - "premium_subscriber": { "type": "boolean" } + "bot_id": { + "type": "string" + }, + "integration_id": { + "type": "string" + }, + "premium_subscriber": { + "type": "boolean" + } }, "additionalProperties": false }, - "id": { "type": "string" } + "id": { + "type": "string" + } }, "required": ["color", "guild", "guild_id", "hoist", "id", "managed", "mentionable", "name", "permissions", "position"] }, @@ -573,22 +3879,44 @@ "items": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "message_notifications": { "type": "integer" }, - "mute_config": { "$ref": "#/components/schemas/MuteConfig" }, - "muted": { "type": "boolean" } + "channel_id": { + "type": "string" + }, + "message_notifications": { + "type": "integer" + }, + "mute_config": { + "$ref": "#/components/schemas/MuteConfig" + }, + "muted": { + "type": "boolean" + } }, "additionalProperties": false, "required": ["channel_id", "message_notifications", "mute_config", "muted"] } }, - "message_notifications": { "type": "integer" }, - "mobile_push": { "type": "boolean" }, - "mute_config": { "$ref": "#/components/schemas/MuteConfig" }, - "muted": { "type": "boolean" }, - "suppress_everyone": { "type": "boolean" }, - "suppress_roles": { "type": "boolean" }, - "version": { "type": "integer" } + "message_notifications": { + "type": "integer" + }, + "mobile_push": { + "type": "boolean" + }, + "mute_config": { + "$ref": "#/components/schemas/MuteConfig" + }, + "muted": { + "type": "boolean" + }, + "suppress_everyone": { + "type": "boolean" + }, + "suppress_roles": { + "type": "boolean" + }, + "version": { + "type": "integer" + } }, "required": [ "channel_overrides", @@ -603,27 +3931,64 @@ }, "MuteConfig": { "type": "object", - "properties": { "end_time": { "type": "integer" }, "selected_time_window": { "type": "integer" } }, + "properties": { + "end_time": { + "type": "integer" + }, + "selected_time_window": { + "type": "integer" + } + }, "required": ["end_time", "selected_time_window"] }, "Webhook": { "type": "object", "properties": { - "id": { "type": "string" }, - "type": { "$ref": "#/components/schemas/WebhookType" }, - "name": { "type": "string" }, - "avatar": { "type": "string" }, - "token": { "type": "string" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "application_id": { "type": "string" }, - "application": { "$ref": "#/components/schemas/Application" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "source_guild_id": { "type": "string" }, - "source_guild": { "$ref": "#/components/schemas/Guild" } + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/WebhookType" + }, + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "token": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "application_id": { + "type": "string" + }, + "application": { + "$ref": "#/components/schemas/Application" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "source_guild_id": { + "type": "string" + }, + "source_guild": { + "$ref": "#/components/schemas/Guild" + } }, "required": [ "application", @@ -640,98 +4005,225 @@ "user_id" ] }, - "WebhookType": { "enum": [1, 2], "type": "number" }, + "WebhookType": { + "enum": [1, 2], + "type": "number" + }, "Application": { "type": "object", "properties": { - "name": { "type": "string" }, - "icon": { "type": "string" }, - "description": { "type": "string" }, - "rpc_origins": { "type": "array", "items": { "type": "string" } }, - "bot_public": { "type": "boolean" }, - "bot_require_code_grant": { "type": "boolean" }, - "terms_of_service_url": { "type": "string" }, - "privacy_policy_url": { "type": "string" }, - "owner": { "$ref": "#/components/schemas/User" }, - "summary": { "type": "string" }, - "verify_key": { "type": "string" }, - "team": { "$ref": "#/components/schemas/Team" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "primary_sku_id": { "type": "string" }, - "slug": { "type": "string" }, - "cover_image": { "type": "string" }, - "flags": { "type": "string" }, - "id": { "type": "string" } + "name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rpc_origins": { + "type": "array", + "items": { + "type": "string" + } + }, + "bot_public": { + "type": "boolean" + }, + "bot_require_code_grant": { + "type": "boolean" + }, + "terms_of_service_url": { + "type": "string" + }, + "privacy_policy_url": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "summary": { + "type": "string" + }, + "verify_key": { + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/Team" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "primary_sku_id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "cover_image": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": ["bot_public", "bot_require_code_grant", "description", "flags", "guild", "id", "name", "verify_key"] }, "Sticker": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "tags": { "type": "string" }, - "pack_id": { "type": "string" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "type": { "$ref": "#/components/schemas/StickerType" }, - "format_type": { "$ref": "#/components/schemas/StickerFormatType" }, - "id": { "type": "string" } + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "pack_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "type": { + "$ref": "#/components/schemas/StickerType" + }, + "format_type": { + "$ref": "#/components/schemas/StickerFormatType" + }, + "id": { + "type": "string" + } }, "required": ["format_type", "id", "name", "pack_id", "tags", "type"] }, - "StickerType": { "enum": [1, 2], "type": "number" }, - "StickerFormatType": { "enum": [1, 2, 3], "type": "number" }, + "StickerType": { + "enum": [1, 2], + "type": "number" + }, + "StickerFormatType": { + "enum": [1, 2, 3], + "type": "number" + }, "Attachment": { "type": "object", "properties": { - "filename": { "type": "string" }, - "size": { "type": "integer" }, - "url": { "type": "string" }, - "proxy_url": { "type": "string" }, - "height": { "type": "integer" }, - "width": { "type": "integer" }, - "content_type": { "type": "string" }, - "message_id": { "type": "string" }, - "message": { "$ref": "#/components/schemas/Message" }, - "id": { "type": "string" } + "filename": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + }, + "content_type": { + "type": "string" + }, + "message_id": { + "type": "string" + }, + "message": { + "$ref": "#/components/schemas/Message" + }, + "id": { + "type": "string" + } }, "required": ["filename", "id", "message", "message_id", "proxy_url", "size", "url"] }, "Embed": { "type": "object", "properties": { - "title": { "type": "string" }, - "type": { "$ref": "#/components/schemas/EmbedType" }, - "description": { "type": "string" }, - "url": { "type": "string" }, - "timestamp": { "type": "string", "format": "date-time" }, - "color": { "type": "integer" }, + "title": { + "type": "string" + }, + "type": { + "enum": ["article", "gifv", "image", "link", "rich", "video"], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, "footer": { "type": "object", "properties": { - "text": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } }, "additionalProperties": false, "required": ["text"] }, - "image": { "$ref": "#/components/schemas/EmbedImage" }, - "thumbnail": { "$ref": "#/components/schemas/EmbedImage" }, - "video": { "$ref": "#/components/schemas/EmbedImage" }, + "image": { + "$ref": "#/components/schemas/EmbedImage" + }, + "thumbnail": { + "$ref": "#/components/schemas/EmbedImage" + }, + "video": { + "$ref": "#/components/schemas/EmbedImage" + }, "provider": { "type": "object", - "properties": { "name": { "type": "string" }, "url": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, "additionalProperties": false }, "author": { "type": "object", "properties": { - "name": { "type": "string" }, - "url": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } }, "additionalProperties": false }, @@ -739,94 +4231,214 @@ "type": "array", "items": { "type": "object", - "properties": { "name": { "type": "string" }, "value": { "type": "string" }, "inline": { "type": "boolean" } }, + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, "additionalProperties": false, "required": ["name", "value"] } } } }, - "EmbedType": { "enum": ["article", "gifv", "image", "link", "rich", "video"], "type": "string" }, + "EmbedType": { + "enum": ["article", "gifv", "image", "link", "rich", "video"], + "type": "string" + }, "EmbedImage": { "type": "object", "properties": { - "url": { "type": "string" }, - "proxy_url": { "type": "string" }, - "height": { "type": "integer" }, - "width": { "type": "integer" } + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } } }, "Reaction": { "type": "object", "properties": { - "count": { "type": "integer" }, - "emoji": { "$ref": "#/components/schemas/PartialEmoji" }, - "user_ids": { "type": "array", "items": { "type": "string" } } + "count": { + "type": "integer" + }, + "emoji": { + "$ref": "#/components/schemas/PartialEmoji" + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + } + } }, "required": ["count", "emoji", "user_ids"] }, "PartialEmoji": { "type": "object", - "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "animated": { "type": "boolean" } }, + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "animated": { + "type": "boolean" + } + }, "required": ["name"] }, - "MessageType": { "enum": [0, 1, 10, 11, 12, 14, 15, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9], "type": "number" }, - "InteractionType": { "enum": [1, 2], "type": "number" }, + "MessageType": { + "enum": [0, 1, 10, 11, 12, 14, 15, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9], + "type": "number" + }, + "InteractionType": { + "enum": [1, 2], + "type": "number" + }, "MessageComponent": { "type": "object", "properties": { - "type": { "type": "integer" }, - "style": { "type": "integer" }, - "label": { "type": "string" }, - "emoji": { "$ref": "#/components/schemas/PartialEmoji" }, - "custom_id": { "type": "string" }, - "url": { "type": "string" }, - "disabled": { "type": "boolean" }, - "components": { "type": "array", "items": { "$ref": "#/components/schemas/MessageComponent" } } + "type": { + "type": "integer" + }, + "style": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "emoji": { + "$ref": "#/components/schemas/PartialEmoji" + }, + "custom_id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageComponent" + } + } }, "required": ["components", "type"] }, "ChannelPermissionOverwrite": { "type": "object", "properties": { - "allow": { "type": "number" }, - "deny": { "type": "number" }, - "id": { "type": "string" }, - "type": { "$ref": "#/components/schemas/ChannelPermissionOverwriteType" } + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + } }, "required": ["allow", "deny", "id", "type"] }, - "ChannelPermissionOverwriteType": { "enum": [0, 1], "type": "number" }, + "ChannelPermissionOverwriteType": { + "enum": [0, 1], + "type": "number" + }, "Ban": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "executor_id": { "type": "string" }, - "executor": { "$ref": "#/components/schemas/User" }, - "ip": { "type": "string" }, - "reason": { "type": "string" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "executor_id": { + "type": "string" + }, + "executor": { + "$ref": "#/components/schemas/User" + }, + "ip": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": ["executor", "executor_id", "guild", "guild_id", "id", "ip", "user", "user_id"] }, "Template": { "type": "object", "properties": { - "code": { "type": "string" }, - "name": { "type": "string" }, - "description": { "type": "string" }, - "usage_count": { "type": "integer" }, - "creator_id": { "type": "string" }, - "creator": { "$ref": "#/components/schemas/User" }, - "created_at": { "type": "string", "format": "date-time" }, - "updated_at": { "type": "string", "format": "date-time" }, - "source_guild_id": { "type": "string" }, - "source_guild": { "$ref": "#/components/schemas/Guild" }, - "serialized_source_guild": { "$ref": "#/components/schemas/Guild" }, - "id": { "type": "string" } + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "usage_count": { + "type": "integer" + }, + "creator_id": { + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/User" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "source_guild_id": { + "type": "string" + }, + "source_guild": { + "$ref": "#/components/schemas/Guild" + }, + "serialized_source_guild": { + "$ref": "#/components/schemas/Guild" + }, + "id": { + "type": "string" + } }, "required": [ "code", @@ -844,37 +4456,89 @@ "Emoji": { "type": "object", "properties": { - "animated": { "type": "boolean" }, - "available": { "type": "boolean" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "managed": { "type": "boolean" }, - "name": { "type": "string" }, - "require_colons": { "type": "boolean" }, - "id": { "type": "string" } + "animated": { + "type": "boolean" + }, + "available": { + "type": "boolean" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "managed": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "require_colons": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": ["animated", "available", "guild", "guild_id", "id", "managed", "name", "require_colons"] }, "Invite": { "type": "object", "properties": { - "code": { "type": "string" }, - "temporary": { "type": "boolean" }, - "uses": { "type": "integer" }, - "max_uses": { "type": "integer" }, - "max_age": { "type": "integer" }, - "created_at": { "type": "string", "format": "date-time" }, - "expires_at": { "type": "string", "format": "date-time" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "inviter_id": { "type": "string" }, - "inviter": { "$ref": "#/components/schemas/User" }, - "target_user_id": { "type": "string" }, - "target_user": { "type": "string" }, - "target_user_type": { "type": "integer" }, - "id": { "type": "string" } + "code": { + "type": "string" + }, + "temporary": { + "type": "boolean" + }, + "uses": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "max_age": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "inviter_id": { + "type": "string" + }, + "inviter": { + "$ref": "#/components/schemas/User" + }, + "target_user_id": { + "type": "string" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + }, + "id": { + "type": "string" + } }, "required": [ "channel", @@ -897,21 +4561,51 @@ "VoiceState": { "type": "object", "properties": { - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "session_id": { "type": "string" }, - "deaf": { "type": "boolean" }, - "mute": { "type": "boolean" }, - "self_deaf": { "type": "boolean" }, - "self_mute": { "type": "boolean" }, - "self_stream": { "type": "boolean" }, - "self_video": { "type": "boolean" }, - "suppress": { "type": "boolean" }, - "id": { "type": "string" } + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "session_id": { + "type": "string" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_mute": { + "type": "boolean" + }, + "self_stream": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + }, + "suppress": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": [ "channel", @@ -939,151 +4633,386 @@ "AuditLogChange": { "type": "object", "properties": { - "new_value": { "$ref": "#/components/schemas/AuditLogChangeValue" }, - "old_value": { "$ref": "#/components/schemas/AuditLogChangeValue" }, - "key": { "type": "string" } + "new_value": { + "$ref": "#/components/schemas/AuditLogChangeValue" + }, + "old_value": { + "$ref": "#/components/schemas/AuditLogChangeValue" + }, + "key": { + "type": "string" + } }, "required": ["key"] }, "AuditLogChangeValue": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "icon_hash": { "type": "string" }, - "splash_hash": { "type": "string" }, - "discovery_splash_hash": { "type": "string" }, - "banner_hash": { "type": "string" }, - "owner_id": { "type": "string" }, - "region": { "type": "string" }, - "preferred_locale": { "type": "string" }, - "afk_channel_id": { "type": "string" }, - "afk_timeout": { "type": "integer" }, - "rules_channel_id": { "type": "string" }, - "public_updates_channel_id": { "type": "string" }, - "mfa_level": { "type": "integer" }, - "verification_level": { "type": "integer" }, - "explicit_content_filter": { "type": "integer" }, - "default_message_notifications": { "type": "integer" }, - "vanity_url_code": { "type": "string" }, - "$add": { "type": "array", "items": { "type": "object", "properties": {} } }, - "$remove": { "type": "array", "items": { "type": "object", "properties": {} } }, - "prune_delete_days": { "type": "integer" }, - "widget_enabled": { "type": "boolean" }, - "widget_channel_id": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "position": { "type": "integer" }, - "topic": { "type": "string" }, - "bitrate": { "type": "integer" }, - "permission_overwrites": { "type": "array", "items": { "$ref": "#/components/schemas/ChannelPermissionOverwrite" } }, - "nsfw": { "type": "boolean" }, - "application_id": { "type": "string" }, - "rate_limit_per_user": { "type": "integer" }, - "permissions": { "type": "string" }, - "color": { "type": "integer" }, - "hoist": { "type": "boolean" }, - "mentionable": { "type": "boolean" }, - "allow": { "type": "string" }, - "deny": { "type": "string" }, - "code": { "type": "string" }, - "channel_id": { "type": "string" }, - "inviter_id": { "type": "string" }, - "max_uses": { "type": "integer" }, - "uses": { "type": "integer" }, - "max_age": { "type": "integer" }, - "temporary": { "type": "boolean" }, - "deaf": { "type": "boolean" }, - "mute": { "type": "boolean" }, - "nick": { "type": "string" }, - "avatar_hash": { "type": "string" }, - "id": { "type": "string" }, - "type": { "type": "integer" }, - "enable_emoticons": { "type": "boolean" }, - "expire_behavior": { "type": "integer" }, - "expire_grace_period": { "type": "integer" }, - "user_limit": { "type": "integer" } + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "icon_hash": { + "type": "string" + }, + "splash_hash": { + "type": "string" + }, + "discovery_splash_hash": { + "type": "string" + }, + "banner_hash": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "afk_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "rules_channel_id": { + "type": "string" + }, + "public_updates_channel_id": { + "type": "string" + }, + "mfa_level": { + "type": "integer" + }, + "verification_level": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "vanity_url_code": { + "type": "string" + }, + "$add": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "$remove": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "prune_delete_days": { + "type": "integer" + }, + "widget_enabled": { + "type": "boolean" + }, + "widget_channel_id": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelPermissionOverwrite" + } + }, + "nsfw": { + "type": "boolean" + }, + "application_id": { + "type": "string" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "permissions": { + "type": "string" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + }, + "code": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "inviter_id": { + "type": "string" + }, + "max_uses": { + "type": "integer" + }, + "uses": { + "type": "integer" + }, + "max_age": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "nick": { + "type": "string" + }, + "avatar_hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "enable_emoticons": { + "type": "boolean" + }, + "expire_behavior": { + "type": "integer" + }, + "expire_grace_period": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + } } }, "AuditLog": { "type": "object", "properties": { - "target": { "$ref": "#/components/schemas/User" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "action_type": { "$ref": "#/components/schemas/AuditLogEvents" }, + "target": { + "$ref": "#/components/schemas/User" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "action_type": { + "$ref": "#/components/schemas/AuditLogEvents" + }, "options": { "type": "object", "properties": { - "delete_member_days": { "type": "string" }, - "members_removed": { "type": "string" }, - "channel_id": { "type": "string" }, - "messaged_id": { "type": "string" }, - "count": { "type": "string" }, - "id": { "type": "string" }, - "type": { "type": "string" }, - "role_name": { "type": "string" } + "delete_member_days": { + "type": "string" + }, + "members_removed": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "messaged_id": { + "type": "string" + }, + "count": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "role_name": { + "type": "string" + } }, "additionalProperties": false }, - "changes": { "type": "array", "items": { "$ref": "#/components/schemas/AuditLogChange" } }, - "reason": { "type": "string" }, - "id": { "type": "string" } + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLogChange" + } + }, + "reason": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": ["action_type", "changes", "id", "user", "user_id"] }, "ReadState": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "last_message_id": { "type": "string" }, - "last_message": { "$ref": "#/components/schemas/Message" }, - "last_pin_timestamp": { "type": "string", "format": "date-time" }, - "mention_count": { "type": "integer" }, - "manual": { "type": "boolean" }, - "id": { "type": "string" } + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "last_message_id": { + "type": "string" + }, + "last_message": { + "$ref": "#/components/schemas/Message" + }, + "last_pin_timestamp": { + "type": "string", + "format": "date-time" + }, + "mention_count": { + "type": "integer" + }, + "manual": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": ["channel", "channel_id", "id", "last_message_id", "manual", "mention_count", "user", "user_id"] }, "UserPublic": { "type": "object", "properties": { - "username": { "type": "string" }, - "discriminator": { "type": "string" }, - "id": { "type": "string" }, - "public_flags": { "type": "string" }, - "avatar": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "bio": { "type": "string" }, - "bot": { "type": "boolean" } + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } }, "required": ["bio", "bot", "discriminator", "id", "public_flags", "username"] }, "UserPrivate": { "type": "object", "properties": { - "locale": { "type": "string" }, - "disabled": { "type": "boolean" }, - "username": { "type": "string" }, - "discriminator": { "type": "string" }, - "id": { "type": "string" }, - "public_flags": { "type": "string" }, - "avatar": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "bio": { "type": "string" }, - "bot": { "type": "boolean" }, - "flags": { "type": "string" }, - "mfa_enabled": { "type": "boolean" }, - "email": { "type": "string" }, - "phone": { "type": "string" }, - "verified": { "type": "boolean" }, - "nsfw_allowed": { "type": "boolean" }, - "premium": { "type": "boolean" }, - "premium_type": { "type": "integer" } + "locale": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "mfa_enabled": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "verified": { + "type": "boolean" + }, + "nsfw_allowed": { + "type": "boolean" + }, + "premium": { + "type": "boolean" + }, + "premium_type": { + "type": "integer" + } }, "required": [ "bio", @@ -1104,50 +5033,111 @@ }, "BanCreateSchema": { "type": "object", - "properties": { "delete_message_days": { "type": "string" }, "reason": { "type": "string" } } + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { + "type": "string" + } + } }, "DmChannelCreateSchema": { "type": "object", - "properties": { "name": { "type": "string" }, "recipients": { "type": "array", "items": { "type": "string" } } }, + "properties": { + "name": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string" + } + } + }, "required": ["recipients"] }, "ChannelModifySchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "type": { "type": "integer" }, - "topic": { "type": "string" }, - "bitrate": { "type": "integer" }, - "user_limit": { "type": "integer" }, - "rate_limit_per_user": { "type": "integer" }, - "position": { "type": "integer" }, + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [0, 1, 10, 11, 12, 13, 2, 3, 4, 5, 6], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, "permission_overwrites": { "type": "array", "items": { "type": "object", "properties": { - "id": { "type": "string" }, - "type": { "type": "integer" }, - "allow": { "type": "number" }, - "deny": { "type": "number" } + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + } }, "additionalProperties": false, "required": ["allow", "deny", "id", "type"] } }, - "parent_id": { "type": "string" }, - "id": { "type": "string" }, - "nsfw": { "type": "boolean" }, - "rtc_region": { "type": "string" }, - "default_auto_archive_duration": { "type": "integer" } - }, - "required": ["name", "type"] + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + } }, "ChannelGuildPositionUpdateSchema": { "type": "array", "items": { "type": "object", - "properties": { "id": { "type": "string" }, "position": { "type": "integer" } }, + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, "additionalProperties": false, "required": ["id"] } @@ -1155,52 +5145,131 @@ "EmojiCreateSchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "image": { "type": "string" }, - "roles": { "type": "array", "items": { "type": "string" } } + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } }, "required": ["image", "name"] }, "GuildCreateSchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "region": { "type": "string" }, - "icon": { "type": "string" }, - "channels": { "type": "array", "items": { "$ref": "#/components/requestBodies/ChannelModifySchema" } }, - "guild_template_code": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "rules_channel_id": { "type": "string" } + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } }, "required": ["name"] }, "GuildUpdateSchema": { "type": "object", "properties": { - "banner": { "type": "string" }, - "splash": { "type": "string" }, - "description": { "type": "string" }, - "features": { "type": "array", "items": { "type": "string" } }, - "verification_level": { "type": "integer" }, - "default_message_notifications": { "type": "integer" }, - "system_channel_flags": { "type": "integer" }, - "explicit_content_filter": { "type": "integer" }, - "public_updates_channel_id": { "type": "string" }, - "afk_timeout": { "type": "integer" }, - "afk_channel_id": { "type": "string" }, - "preferred_locale": { "type": "string" }, - "name": { "type": "string" }, - "region": { "type": "string" }, - "icon": { "type": "string" }, - "guild_template_code": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "rules_channel_id": { "type": "string" } + "banner": { + "type": "string", + "nullable": true + }, + "splash": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } }, "required": ["name"] }, "GuildTemplateCreateSchema": { "type": "object", - "properties": { "name": { "type": "string" }, "avatar": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true + } + }, "required": ["name"] }, "GuildUpdateWelcomeScreenSchema": { @@ -1211,225 +5280,411 @@ "items": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "description": { "type": "string" }, - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" } + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } }, "additionalProperties": false, "required": ["channel_id", "description", "emoji_name"] } }, - "enabled": { "type": "boolean" }, - "description": { "type": "string" } + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + } } }, "InviteCreateSchema": { "type": "object", "properties": { - "target_user_id": { "type": "string" }, - "target_type": { "type": "string" }, - "validate": { "type": "string" }, - "max_age": { "type": "integer" }, - "max_uses": { "type": "integer" }, - "temporary": { "type": "boolean" }, - "unique": { "type": "boolean" }, - "target_user": { "type": "string" }, - "target_user_type": { "type": "integer" } + "target_user_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } } }, "MemberCreateSchema": { "type": "object", "properties": { - "id": { "type": "string" }, - "nick": { "type": "string" }, - "guild_id": { "type": "string" }, - "joined_at": { "type": "string", "format": "date-time" } + "id": { + "type": "string" + }, + "nick": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "joined_at": { + "type": "string", + "format": "date-time" + } }, "required": ["guild_id", "id", "joined_at", "nick"] }, - "MemberNickChangeSchema": { "type": "object", "properties": { "nick": { "type": "string" } }, "required": ["nick"] }, - "MemberChangeSchema": { "type": "object", "properties": { "roles": { "type": "array", "items": { "type": "string" } } } }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "required": ["nick"] + }, + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "MessageCreateSchema": { "type": "object", "properties": { - "content": { "type": "string" }, - "nonce": { "type": "string" }, - "tts": { "type": "boolean" }, - "flags": { "type": "string" }, + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embed" + } + }, "embed": { - "additionalProperties": false, + "$ref": "#/components/schemas/Embed" + }, + "allowed_mentions": { "type": "object", "properties": { - "title": { "type": "string" }, - "type": { "$ref": "#/components/requestBodies/EmbedType" }, - "description": { "type": "string" }, - "url": { "type": "string" }, - "timestamp": { "type": "string" }, - "color": { "type": "integer" }, - "footer": { - "type": "object", - "properties": { - "text": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } - }, - "additionalProperties": false, - "required": ["text"] - }, - "image": { "$ref": "#/components/requestBodies/EmbedImage" }, - "thumbnail": { "$ref": "#/components/requestBodies/EmbedImage" }, - "video": { "$ref": "#/components/requestBodies/EmbedImage" }, - "provider": { - "type": "object", - "properties": { "name": { "type": "string" }, "url": { "type": "string" } }, - "additionalProperties": false + "parse": { + "type": "array", + "items": { + "type": "string" + } }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "url": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } - }, - "additionalProperties": false + "roles": { + "type": "array", + "items": { + "type": "string" + } }, - "fields": { + "users": { "type": "array", "items": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "value": { "type": "string" }, - "inline": { "type": "boolean" } - }, - "additionalProperties": false, - "required": ["name", "value"] + "type": "string" } + }, + "replied_user": { + "type": "boolean" } - } - }, - "allowed_mentions": { - "type": "object", - "properties": { - "parse": { "type": "array", "items": { "type": "string" } }, - "roles": { "type": "array", "items": { "type": "string" } }, - "users": { "type": "array", "items": { "type": "string" } }, - "replied_user": { "type": "boolean" } }, "additionalProperties": false }, "message_reference": { "type": "object", "properties": { - "message_id": { "type": "string" }, - "channel_id": { "type": "string" }, - "guild_id": { "type": "string" }, - "fail_if_not_exists": { "type": "boolean" } + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "fail_if_not_exists": { + "type": "boolean" + } }, "additionalProperties": false, "required": ["channel_id", "message_id"] }, - "payload_json": { "type": "string" }, - "file": {} + "payload_json": { + "type": "string" + }, + "file": {}, + "attachments": { + "type": "array", + "items": {} + } } }, "RoleModifySchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "permissions": { "type": "number" }, - "color": { "type": "integer" }, - "hoist": { "type": "boolean" }, - "mentionable": { "type": "boolean" }, - "position": { "type": "integer" } + "name": { + "type": "string" + }, + "permissions": { + "type": "number" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } } }, "TemplateCreateSchema": { "type": "object", - "properties": { "name": { "type": "string" }, "description": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, "required": ["name"] }, "TemplateModifySchema": { "type": "object", - "properties": { "name": { "type": "string" }, "description": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, "required": ["name"] }, "UserModifySchema": { "type": "object", "properties": { - "username": { "type": "string" }, - "avatar": { "type": "string" }, - "bio": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "password": { "type": "string" }, - "new_password": { "type": "string" }, - "code": { "type": "string" } + "username": { + "minLength": 1, + "maxLength": 100, + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true + }, + "bio": { + "maxLength": 1024, + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string", + "nullable": true + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" + } } }, "UserSettingsSchema": { "type": "object", "properties": { - "afk_timeout": { "type": "integer" }, - "allow_accessibility_detection": { "type": "boolean" }, - "animate_emoji": { "type": "boolean" }, - "animate_stickers": { "type": "integer" }, - "contact_sync_enabled": { "type": "boolean" }, - "convert_emoticons": { "type": "boolean" }, + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, "custom_status": { "type": "object", "properties": { - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" }, - "expires_at": { "type": "integer" }, - "text": { "type": "string" } + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } }, "additionalProperties": false }, - "default_guilds_restricted": { "type": "boolean" }, - "detect_platform_accounts": { "type": "boolean" }, - "developer_mode": { "type": "boolean" }, - "disable_games_tab": { "type": "boolean" }, - "enable_tts_command": { "type": "boolean" }, - "explicit_content_filter": { "type": "integer" }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, "friend_source_flags": { "type": "object", - "properties": { "all": { "type": "boolean" } }, + "properties": { + "all": { + "type": "boolean" + } + }, "additionalProperties": false, "required": ["all"] }, - "gateway_connected": { "type": "boolean" }, - "gif_auto_play": { "type": "boolean" }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, "guild_folders": { "type": "array", "items": { "type": "object", "properties": { - "color": { "type": "integer" }, - "guild_ids": { "type": "array", "items": { "type": "string" } }, - "id": { "type": "integer" }, - "name": { "type": "string" } + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } }, "additionalProperties": false, "required": ["color", "guild_ids", "id", "name"] } }, - "guild_positions": { "type": "array", "items": { "type": "string" } }, - "inline_attachment_media": { "type": "boolean" }, - "inline_embed_media": { "type": "boolean" }, - "locale": { "type": "string" }, - "message_display_compact": { "type": "boolean" }, - "native_phone_integration_enabled": { "type": "boolean" }, - "render_embeds": { "type": "boolean" }, - "render_reactions": { "type": "boolean" }, - "restricted_guilds": { "type": "array", "items": { "type": "string" } }, - "show_current_game": { "type": "boolean" }, - "status": { "enum": ["dnd", "idle", "offline", "online"], "type": "string" }, - "stream_notifications_enabled": { "type": "boolean" }, - "theme": { "enum": ["dark", "white"], "type": "string" }, - "timezone_offset": { "type": "integer" } + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": ["dnd", "idle", "offline", "online"], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": ["dark", "white"], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } }, "required": [ "afk_timeout", @@ -1467,8 +5722,263 @@ }, "WidgetModifySchema": { "type": "object", - "properties": { "enabled": { "type": "boolean" }, "channel_id": { "type": "string" } }, + "properties": { + "enabled": { + "type": "boolean" + }, + "channel_id": { + "type": "string" + } + }, "required": ["channel_id", "enabled"] + }, + "RegisterSchema": { + "type": "object", + "properties": { + "username": { + "minLength": 2, + "maxLength": 32, + "type": "string" + }, + "password": { + "minLength": 1, + "maxLength": 72, + "type": "string" + }, + "consent": { + "type": "boolean" + }, + "email": { + "format": "email", + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "invite": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + }, + "captcha_key": { + "type": "string" + } + }, + "required": ["consent", "username"] + }, + "LoginSchema": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "undelete": { + "type": "boolean" + }, + "captcha_key": { + "type": "string" + }, + "login_source": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + } + }, + "required": ["login", "password"] + }, + "MessageAcknowledgeSchema": { + "type": "object", + "properties": { + "manual": { + "type": "boolean" + }, + "mention_count": { + "type": "integer" + } + } + }, + "BulkDeleteSchema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["messages"] + }, + "ChannelPermissionOverwriteSchema": { + "type": "object", + "properties": { + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + } + }, + "required": ["allow", "deny", "id", "type"] + }, + "WebhookCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 80, + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "required": ["avatar", "name"] + }, + "ChannelReorderSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "lock_permissions": { + "type": "boolean" + }, + "parent_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": ["id"] + } + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": ["id", "position"] + } + }, + "VanityUrlSchema": { + "type": "object", + "properties": { + "code": { + "minLength": 1, + "maxLength": 20, + "type": "string" + } + } + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string", + "format": "date-time" + }, + "self_mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + } + }, + "required": ["channel_id"] + }, + "UserProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/UserPublic" + }, + "connected_accounts": { + "$ref": "#/components/schemas/PublicConnectedAccount" + }, + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "required": ["connected_accounts", "user"] + }, + "RelationshipPutSchema": { + "type": "object", + "properties": { + "type": { + "enum": [1, 2, 3, 4], + "type": "number" + } + } + }, + "RelationshipPostSchema": { + "type": "object", + "properties": { + "discriminator": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": ["discriminator", "username"] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "required": ["name", "type", "verifie"] } }, "requestBodies": {}, @@ -1482,4 +5992,4 @@ "links": {}, "callbacks": {} } -} \ No newline at end of file +} diff --git a/api/assets/responses.json b/api/assets/responses.json deleted file mode 100644 index 35645d73..00000000 --- a/api/assets/responses.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "UserProfileResponse": { - "type": "object", - "properties": { - "user": { - "$ref": "#/definitions/UserPublic" - }, - "connected_accounts": { - "$ref": "#/definitions/PublicConnectedAccount" - }, - "premium_guild_since": { - "type": "string", - "format": "date-time" - }, - "premium_since": { - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false, - "required": [ - "connected_accounts", - "user" - ], - "definitions": { - "UserPublic": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "discriminator": { - "type": "string" - }, - "id": { - "type": "string" - }, - "public_flags": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, - "bot": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "bio", - "bot", - "discriminator", - "id", - "public_flags", - "username" - ] - }, - "PublicConnectedAccount": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "verifie": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "name", - "type", - "verifie" - ] - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" - } -} \ No newline at end of file diff --git a/api/assets/schemas.json b/api/assets/schemas.json index cc45ebb3..76ad3b16 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -385,6 +385,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -698,6 +759,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -960,6 +1082,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1201,6 +1384,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1445,6 +1689,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1698,6 +2003,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1944,17 +2310,78 @@ } }, "additionalProperties": false - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "BanCreateSchema": { - "type": "object", - "properties": { - "delete_message_days": { - "type": "string" }, - "reason": { + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "BanCreateSchema": { + "type": "object", + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { "type": "string" } }, @@ -2185,6 +2612,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2438,6 +2926,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2704,6 +3253,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3009,6 +3619,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3250,6 +3921,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3491,6 +4223,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3744,6 +4537,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3992,6 +4846,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4236,6 +5151,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4480,6 +5456,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4720,6 +5757,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4980,16 +6078,77 @@ } }, "additionalProperties": false - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "GuildUpdateWelcomeScreenSchema": { - "type": "object", - "properties": { - "welcome_channels": { - "type": "array", - "items": { + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateWelcomeScreenSchema": { + "type": "object", + "properties": { + "welcome_channels": { + "type": "array", + "items": { "type": "object", "properties": { "channel_id": { @@ -5247,6 +6406,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5492,6 +6712,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5502,16 +6783,330 @@ "name": { "type": "string" }, - "avatar": { - "type": [ - "null", - "string" - ] + "avatar": { + "type": [ + "null", + "string" + ] + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/UserPublic" + }, + "connected_accounts": { + "$ref": "#/definitions/PublicConnectedAccount" + }, + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" } }, "additionalProperties": false, "required": [ - "name" + "connected_accounts", + "user" ], "definitions": { "ChannelPermissionOverwriteType": { @@ -5739,6 +7334,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5986,6 +7642,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6254,6 +7971,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6498,6 +8276,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6743,6 +8582,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -7171,6 +9071,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/api/jest/getRouteDescriptions.js b/api/jest/getRouteDescriptions.js new file mode 100644 index 00000000..4f8d2e75 --- /dev/null +++ b/api/jest/getRouteDescriptions.js @@ -0,0 +1,66 @@ +const { traverseDirectory } = require("lambert-server"); +const path = require("path"); +const express = require("express"); +const RouteUtility = require("../dist/util/route"); +const Router = express.Router; + +/** + * Some documentation. + * + * @type {Map} + */ +const routes = new Map(); +let currentPath = ""; +let currentFile = ""; +const methods = ["get", "post", "put", "delete", "patch"]; + +function registerPath(file, method, prefix, path, ...args) { + const urlPath = prefix + path; + const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); + const opts = args.find((x) => typeof x === "object"); + if (opts) { + routes.set(urlPath + "|" + method, opts); // @ts-ignore + opts.file = sourceFile; + // console.log(method, urlPath, opts); + } else { + console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`); + } +} + +function routeOptions(opts) { + return opts; +} + +// @ts-ignore +RouteUtility.route = routeOptions; + +express.Router = (opts) => { + const path = currentPath; + const file = currentFile; + const router = Router(opts); + + for (const method of methods) { + router[method] = registerPath.bind(null, file, method, path); + } + + return router; +}; + +module.exports = function getRouteDescriptions() { + const root = path.join(__dirname, "..", "dist", "routes", "/"); + traverseDirectory({ dirname: root, recursive: true }, (file) => { + currentFile = file; + let path = file.replace(root.slice(0, -1), ""); + path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path + path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes + if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path + currentPath = path; + + try { + require(file); + } catch (error) { + console.error("error loading file " + file, error); + } + }); + return routes; +}; diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.ts deleted file mode 100644 index 33922899..00000000 --- a/api/jest/getRouteDescriptions.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { traverseDirectory } from "lambert-server"; -import path from "path"; -import express from "express"; -import * as RouteUtility from "../dist/util/route"; -import { RouteOptions } from "../dist/util/route"; -const Router = express.Router; - -const routes = new Map(); -let currentPath = ""; -let currentFile = ""; -const methods = ["get", "post", "put", "delete", "patch"]; - -function registerPath(file, method, prefix, path, ...args) { - const urlPath = prefix + path; - const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); - const opts: RouteOptions = args.find((x) => typeof x === "object"); - if (opts) { - routes.set(urlPath + "|" + method, opts); // @ts-ignore - opts.file = sourceFile; - // console.log(method, urlPath, opts); - } else { - console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); - } -} - -function routeOptions(opts) { - return opts; -} - -// @ts-ignore -RouteUtility.route = routeOptions; - -express.Router = (opts) => { - const path = currentPath; - const file = currentFile; - const router = Router(opts); - - for (const method of methods) { - router[method] = registerPath.bind(null, file, method, path); - } - - return router; -}; - -export default function getRouteDescriptions() { - const root = path.join(__dirname, "..", "dist", "routes", "/"); - traverseDirectory({ dirname: root, recursive: true }, (file) => { - currentFile = file; - let path = file.replace(root.slice(0, -1), ""); - path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path - path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes - if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path - currentPath = path; - - require(file); - }); - return routes; -} diff --git a/api/scripts/generate_body_schema.js b/api/scripts/generate_body_schema.js new file mode 100644 index 00000000..22d0b02e --- /dev/null +++ b/api/scripts/generate_body_schema.js @@ -0,0 +1,70 @@ +// 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 schemaPath = path.join(__dirname, "..", "assets", "schemas.json"); + +const settings = { + required: true, + ignoreErrors: true, + excludePrivate: true, + defaultNumberType: "integer", + noExtraProps: true, + defaultProps: false +}; +const compilerOptions = { + strictNullChecks: true +}; +const Excluded = [ + "DefaultSchema", + "Schema", + "EntitySchema", + "ServerResponse", + "Http2ServerResponse", + "global.Express.Response", + "Response", + "e.Response", + "request.Response", + "supertest.Response" +]; + +function main() { + const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); + const generator = TJS.buildGenerator(program, settings); + if (!generator || !program) return; + + const schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x)); + console.log(schemas); + + var definitions = {}; + + for (const name of schemas) { + const part = TJS.generateSchema(program, name, settings, [], generator); + if (!part) continue; + + definitions = { ...definitions, [name]: { ...part } }; + } + + fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); +} + +main(); + +function walk(dir) { + var results = []; + var list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + "/" + file; + var stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file)); + } else { + if (!file.endsWith(".ts")) return; + results.push(file); + } + }); + return results; +} diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts deleted file mode 100644 index 316e5a69..00000000 --- a/api/scripts/generate_body_schema.ts +++ /dev/null @@ -1,60 +0,0 @@ -// 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 schemaPath = path.join(__dirname, "..", "assets", "schemas.json"); - -const settings: TJS.PartialArgs = { - required: true, - ignoreErrors: true, - excludePrivate: true, - defaultNumberType: "integer", - noExtraProps: true, - defaultProps: false -}; -const compilerOptions: TJS.CompilerOptions = { - strictNullChecks: true -}; -const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"]; - -function main() { - const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); - const generator = TJS.buildGenerator(program, settings); - if (!generator || !program) return; - - const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)); - console.log(schemas); - - var definitions: any = {}; - - for (const name of schemas) { - const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); - if (!part) continue; - - definitions = { ...definitions, [name]: { ...part } }; - } - - fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); -} - -// #/definitions/ -main(); - -function walk(dir: string) { - var results = [] as string[]; - var list = fs.readdirSync(dir); - list.forEach(function (file) { - file = dir + "/" + file; - var stat = fs.statSync(file); - if (stat && stat.isDirectory()) { - /* Recurse into a subdirectory */ - results = results.concat(walk(file)); - } else { - if (!file.endsWith(".ts")) return; - results.push(file); - } - }); - return results; -} diff --git a/api/scripts/generate_openapi_schema.js b/api/scripts/generate_openapi_schema.js new file mode 100644 index 00000000..eb979f14 --- /dev/null +++ b/api/scripts/generate_openapi_schema.js @@ -0,0 +1,127 @@ +// https://mermade.github.io/openapi-gui/# +// https://editor.swagger.io/ +const getRouteDescriptions = require("../jest/getRouteDescriptions"); +const path = require("path"); +const fs = require("fs"); +require("missing-native-js-functions"); + +const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); +const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); +const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); +const specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" })); + +function combineSchemas(schemas) { + var definitions = {}; + + for (const name in schemas) { + definitions = { + ...definitions, + ...schemas[name].definitions, + [name]: { ...schemas[name], definitions: undefined, $schema: undefined } + }; + } + + for (const key in definitions) { + specification.components.schemas[key] = definitions[key]; + delete definitions[key].additionalProperties; + delete definitions[key].$schema; + const definition = definitions[key]; + + if (typeof definition.properties === "object") { + for (const property of Object.values(definition.properties)) { + if (Array.isArray(property.type)) { + if (property.type.includes("null")) { + property.type = property.type.find((x) => x !== "null"); + property.nullable = true; + } + } + } + } + } + + return definitions; +} + +function getTag(key) { + return key.match(/\/([\w-]+)/)[1]; +} + +function apiRoutes() { + const routes = getRouteDescriptions(); + + const tags = Array.from(routes.keys()).map((x) => getTag(x)); + specification.tags = [...specification.tags.map((x) => x.name), ...tags].unique().map((x) => ({ name: x })); + + routes.forEach((route, pathAndMethod) => { + const [p, method] = pathAndMethod.split("|"); + const path = p.replace(/:(\w+)/g, "{$1}"); + + let obj = specification.paths[path]?.[method] || {}; + if (!obj.description) { + const permission = route.permission ? `##### Requires the \`\`${route.permission}\`\` permission\n` : ""; + const event = route.test?.event ? `##### Fires a \`\`${route.test?.event}\`\` event\n` : ""; + obj.description = permission + event; + } + if (route.body) { + obj.requestBody = { + required: true, + content: { + "application/json": { + schema: { $ref: `#/components/schemas/${route.body}` } + } + } + }.merge(obj.requestBody); + } + if (!obj.responses) { + obj.responses = { + default: { + description: "not documented" + } + }; + } + if (route.test?.response) { + const status = route.test.response.status || 200; + obj.responses = { + [status]: { + ...(route.test.response.body + ? { + description: obj.responses[status].description || "", + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${route.test.response.body}` + } + } + } + } + : {}) + } + }.merge(obj.responses); + delete obj.responses.default; + } + if (p.includes(":")) { + obj.parameters = p.match(/:\w+/g)?.map((x) => ({ + name: x.replace(":", ""), + in: "path", + required: true, + schema: { type: "string" }, + description: x.replace(":", "") + })); + } + obj.tags = [...(obj.tags || []), getTag(p)].unique(); + + specification.paths[path] = { ...specification.paths[path], [method]: obj }; + }); +} + +function main() { + combineSchemas(schemas); + apiRoutes(); + + fs.writeFileSync( + openapiPath, + JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number") + ); +} + +main(); diff --git a/api/scripts/generate_openapi_schema.ts b/api/scripts/generate_openapi_schema.ts deleted file mode 100644 index c0995b6c..00000000 --- a/api/scripts/generate_openapi_schema.ts +++ /dev/null @@ -1,92 +0,0 @@ -// 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 utilSchemas() { - const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "..", "util", "src", "index.ts")], compilerOptions); - const generator = TJS.buildGenerator(program, settings); - - const schemas = ["UserPublic", "UserPrivate", "PublicConnectedAccount"]; - - // @ts-ignore - combineSchemas({ schemas, generator, program }); -} - -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, [name]: { ...part, definitions: undefined, $schema: undefined } }; - } - - for (const key in definitions) { - specification.components.schemas[key] = definitions[key]; - delete definitions[key].additionalProperties; - delete definitions[key].$schema; - } - - return definitions; -} - -const ExcludedSchemas = [ - "DefaultSchema", - "Schema", - "EntitySchema", - "ServerResponse", - "Http2ServerResponse", - "global.Express.Response", - "Response", - "e.Response", - "request.Response", - "supertest.Response" -]; - -function apiSchemas() { - const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions); - const generator = TJS.buildGenerator(program, settings); - - const schemas = generator - .getUserSymbols() - .filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x)) - .concat(generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x))); - - // @ts-ignore - combineSchemas({ schemas, generator, program }); -} - -function addDefaultResponses() { - Object.values(specification.paths).forEach((path: any) => Object.values(path).forEach((request: any) => {})); -} - -function main() { - addDefaultResponses(); - utilSchemas(); - apiSchemas(); - - fs.writeFileSync( - openapiPath, - JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number") - ); -} - -main(); diff --git a/api/scripts/generate_test_schema.ts b/api/scripts/generate_test_schema.ts deleted file mode 100644 index eed77738..00000000 --- a/api/scripts/generate_test_schema.ts +++ /dev/null @@ -1,68 +0,0 @@ -// 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 schemaPath = path.join(__dirname, "..", "assets", "responses.json"); - -const settings: TJS.PartialArgs = { - required: true, - ignoreErrors: true, - excludePrivate: true, - defaultNumberType: "integer", - noExtraProps: true, - defaultProps: false -}; -const compilerOptions: TJS.CompilerOptions = { - strictNullChecks: true -}; -const ExcludedSchemas = [ - "ServerResponse", - "Http2ServerResponse", - "global.Express.Response", - "Response", - "e.Response", - "request.Response", - "supertest.Response" -]; - -function main() { - const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); - const generator = TJS.buildGenerator(program, settings); - if (!generator || !program) return; - - const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x)); - console.log(schemas); - - var definitions: any = {}; - - for (const name of schemas) { - const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); - if (!part) continue; - - definitions = { ...definitions, [name]: { ...part } }; - } - - fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); -} - -// #/definitions/ -main(); - -function walk(dir: string) { - var results = [] as string[]; - var list = fs.readdirSync(dir); - list.forEach(function (file) { - file = dir + "/" + file; - var stat = fs.statSync(file); - if (stat && stat.isDirectory()) { - /* Recurse into a subdirectory */ - results = results.concat(walk(file)); - } else { - if (!file.endsWith(".ts")) return; - results.push(file); - } - }); - return results; -} diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index f672658a..ff04f8aa 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -1,9 +1,7 @@ import { Request, Response, Router } from "express"; import { FieldErrors, route } from "@fosscord/api"; import bcrypt from "bcrypt"; -import jwt from "jsonwebtoken"; -import { Config, User } from "@fosscord/util"; -import { adjustEmail } from "./register"; +import { Config, User, generateToken, adjustEmail } from "@fosscord/util"; const router: Router = Router(); export default router; @@ -68,25 +66,6 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo res.json({ token, settings: user.settings }); }); -export async function generateToken(id: string) { - const iat = Math.floor(Date.now() / 1000); - const algorithm = "HS256"; - - return new Promise((res, rej) => { - jwt.sign( - { id: id, iat }, - Config.get().security.jwtSecret, - { - algorithm - }, - (err, token) => { - if (err) return rej(err); - return res(token); - } - ); - }); -} - /** * POST /auth/login * @argument { login: "email@gmail.com", password: "cleartextpassword", undelete: false, captcha_key: null, login_source: null, gift_code_sku_id: null, } diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 4d3f2860..9c058399 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,10 +1,8 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, Config, defaultSettings, Member, Invite } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config, defaultSettings, generateToken, Invite, adjustEmail } from "@fosscord/util"; import bcrypt from "bcrypt"; -import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api"; +import { FieldErrors, route, getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import "missing-native-js-functions"; -import { generateToken } from "./login"; -import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import { HTTPError } from "lambert-server"; const router: Router = Router(); @@ -228,24 +226,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re return res.json({ token: await generateToken(user.id) }); }); -export function adjustEmail(email: string): string | undefined { - if (!email) return email; - // body parser already checked if it is a valid email - const parts = email.match(EMAIL_REGEX); - // @ts-ignore - if (!parts || parts.length < 5) return undefined; - const domain = parts[5]; - const user = parts[1]; - - // TODO: check accounts with uncommon email domains - if (domain === "gmail.com" || domain === "googlemail.com") { - // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator - return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; - } - - return email; -} - export default router; /** diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index c7beeee8..83b62049 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -1,9 +1,10 @@ import { Request, Response, Router } from "express"; import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; +import { route } from "@fosscord/api" const router: Router = Router(); -router.put("/:user_id", async (req: Request, res: Response) => { +router.put("/:user_id", route({}), async (req: Request, res: Response) => { const { channel_id, user_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); @@ -39,7 +40,7 @@ router.put("/:user_id", async (req: Request, res: Response) => { } }); -router.delete("/:user_id", async (req: Request, res: Response) => { +router.delete("/:user_id", route({}), async (req: Request, res: Response) => { const { channel_id, user_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index ae10be82..8f5ca7ba 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -4,14 +4,14 @@ import { Request, Response, Router } from "express"; const router = Router(); -router.delete("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); -router.put("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; await Member.addRole(member_id, guild_id, role_id); diff --git a/api/src/routes/sticker-packs/#id/index.ts b/api/src/routes/sticker-packs/#id/index.ts index 2344a48f..7f723e97 100644 --- a/api/src/routes/sticker-packs/#id/index.ts +++ b/api/src/routes/sticker-packs/#id/index.ts @@ -1,8 +1,9 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json({ id: "", @@ -15,4 +16,4 @@ router.get("/", async (req: Request, res: Response) => { }).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/sticker-packs/index.ts b/api/src/routes/sticker-packs/index.ts index 6c4e46d8..d671c161 100644 --- a/api/src/routes/sticker-packs/index.ts +++ b/api/src/routes/sticker-packs/index.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json({ sticker_packs: [] }).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/util/String.ts b/api/src/util/String.ts index 2fe32d2c..67d87e37 100644 --- a/api/src/util/String.ts +++ b/api/src/util/String.ts @@ -1,8 +1,6 @@ import { Request } from "express"; import { ntob } from "./Base64"; import { FieldErrors } from "./FieldError"; -export const EMAIL_REGEX = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { if (str.length < min || str.length > max) { diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index 0473c579..a9c75df1 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -9,7 +9,7 @@ import addFormats from "ajv-formats"; import fetch from "node-fetch"; import { User } from "@fosscord/util"; -const SchemaPath = join(__dirname, "..", "assets", "responses.json"); +const SchemaPath = join(__dirname, "..", "assets", "schemas.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ allErrors: true, @@ -64,7 +64,7 @@ describe("Automatic unit tests with route description middleware", () => { routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); - test(path, async (done) => { + test(`${method.toUpperCase()} ${path}`, async (done) => { if (!route.test) { console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); diff --git a/util/src/util/Email.ts b/util/src/util/Email.ts new file mode 100644 index 00000000..c304f584 --- /dev/null +++ b/util/src/util/Email.ts @@ -0,0 +1,20 @@ +export const EMAIL_REGEX = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + +export function adjustEmail(email: string): string | undefined { + if (!email) return email; + // body parser already checked if it is a valid email + const parts = email.match(EMAIL_REGEX); + // @ts-ignore + if (!parts || parts.length < 5) return undefined; + const domain = parts[5]; + const user = parts[1]; + + // TODO: check accounts with uncommon email domains + if (domain === "gmail.com" || domain === "googlemail.com") { + // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator + return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; + } + + return email; +} diff --git a/util/src/util/Token.ts b/util/src/util/Token.ts new file mode 100644 index 00000000..111d59a2 --- /dev/null +++ b/util/src/util/Token.ts @@ -0,0 +1,43 @@ +import jwt, { VerifyOptions } from "jsonwebtoken"; +import { Config } from "./Config"; +import { User } from "../entities"; + +export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; + +export function checkToken(token: string, jwtSecret: string): Promise { + return new Promise((res, rej) => { + token = token.replace("Bot ", ""); // TODO: proper bot support + jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => { + if (err || !decoded) return rej("Invalid Token"); + + const user = await User.findOne({ id: decoded.id }, { select: ["data", "bot", "disabled", "deleted"] }); + if (!user) return rej("Invalid Token"); + // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds + if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) + return rej("Invalid Token"); + if (user.disabled) return rej("User disabled"); + if (user.deleted) return rej("User not found"); + + return res({ decoded, user }); + }); + }); +} + +export async function generateToken(id: string) { + const iat = Math.floor(Date.now() / 1000); + const algorithm = "HS256"; + + return new Promise((res, rej) => { + jwt.sign( + { id: id, iat }, + Config.get().security.jwtSecret, + { + algorithm, + }, + (err, token) => { + if (err) return rej(err); + return res(token); + } + ); + }); +} diff --git a/util/src/util/checkToken.ts b/util/src/util/checkToken.ts deleted file mode 100644 index 8415e8c0..00000000 --- a/util/src/util/checkToken.ts +++ /dev/null @@ -1,23 +0,0 @@ -import jwt, { VerifyOptions } from "jsonwebtoken"; -import { User } from "../entities"; - -export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; - -export function checkToken(token: string, jwtSecret: string): Promise { - return new Promise((res, rej) => { - token = token.replace("Bot ", ""); // TODO: proper bot support - jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => { - if (err || !decoded) return rej("Invalid Token"); - - const user = await User.findOne({ id: decoded.id }, { select: ["data", "bot", "disabled", "deleted"] }); - if (!user) return rej("Invalid Token"); - // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds - if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) - return rej("Invalid Token"); - if (user.disabled) return rej("User disabled"); - if (user.deleted) return rej("User not found"); - - return res({ decoded, user }); - }); - }); -} diff --git a/util/src/util/index.ts b/util/src/util/index.ts index 3160380f..d73bf4ca 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts @@ -1,11 +1,12 @@ export * from "./ApiError"; export * from "./BitField"; -export * from "./checkToken"; +export * from "./Token"; export * from "./cdn"; export * from "./Config"; export * from "./Constants"; export * from "./Database"; export * from "./Event"; +export * from "./Email"; export * from "./Intents"; export * from "./MessageFlags"; export * from "./Permissions"; -- cgit 1.5.1