diff --git a/.lintstagedrc b/.lintstagedrc
deleted file mode 100644
index a8a47eb31..000000000
--- a/.lintstagedrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "*.{j,t}s": ["eslint", "prettier --write"],
- "src/schemas/{*,**/*}.ts": ["eslint", "prettier --write", "tsc -b -v", "node scripts/schema.js", "node scripts/openapi.js" ]
-}
diff --git a/.lintstagedrc.js b/.lintstagedrc.js
new file mode 100644
index 000000000..76ea0b0a1
--- /dev/null
+++ b/.lintstagedrc.js
@@ -0,0 +1,4 @@
+module.exports = {
+ "*.{j,t}s": ["eslint", "prettier --write"],
+ "src/schemas/{*,**/*}.ts": [() => "tsc -b -v", () => "node scripts/schema.js", () => "node scripts/openapi.js"],
+};
diff --git a/.prettierrc.json b/.prettierrc.json
index 6172f0cfc..ebfbac37b 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,13 +1,13 @@
{
- "trailingComma": "all",
- "tabWidth": 4,
- "semi": true,
- "arrowParens": "always",
- "bracketSameLine": false,
- "bracketSpacing": true,
- "quoteProps": "as-needed",
- "useTabs": false,
- "singleQuote": false,
- "endOfLine": "auto",
- "printWidth": 180
+ "trailingComma": "all",
+ "tabWidth": 4,
+ "semi": true,
+ "arrowParens": "always",
+ "bracketSameLine": false,
+ "bracketSpacing": true,
+ "quoteProps": "as-needed",
+ "useTabs": false,
+ "singleQuote": false,
+ "endOfLine": "auto",
+ "printWidth": 180
}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index d0c065832..17e64dc88 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -9,35 +9,35 @@ import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
- baseDirectory: __dirname,
- recommendedConfig: js.configs.recommended,
- allConfig: js.configs.all,
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
});
export default [
- {
- ignores: ["**/node_modules", "**/dist", "**/README.md", "**/COPYING", "**/scripts/", "**/assets", "**/extra/"],
- },
- ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
- {
- plugins: {
- "@typescript-eslint": typescriptEslint,
- },
+ {
+ ignores: ["**/node_modules", "**/dist", "**/README.md", "**/COPYING", "**/scripts/", "**/assets", "**/extra/"],
+ },
+ ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
+ {
+ plugins: {
+ "@typescript-eslint": typescriptEslint,
+ },
- languageOptions: {
- globals: {
- ...globals.node,
- },
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ },
- parser: tsParser,
- },
+ parser: tsParser,
+ },
- rules: {
- "no-mixed-spaces-and-tabs": "off",
- "@typescript-eslint/no-inferrable-types": "off", // Required by typeorm
- "@typescript-eslint/no-var-requires": "off", // Sometimes requred by typeorm to resolve circular deps
- "@typescript-eslint/no-require-imports": "off",
- "@typescript-eslint/no-unused-vars": "off",
- },
- },
+ rules: {
+ "no-mixed-spaces-and-tabs": "off",
+ "@typescript-eslint/no-inferrable-types": "off", // Required by typeorm
+ "@typescript-eslint/no-var-requires": "off", // Sometimes requred by typeorm to resolve circular deps
+ "@typescript-eslint/no-require-imports": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ },
+ },
];
diff --git a/package-lock.json b/package-lock.json
index ed7b29c4e..2c0a69863 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/package.json b/package.json
index 5482ae76c..c08b79ad5 100644
--- a/package.json
+++ b/package.json
@@ -1,135 +1,135 @@
{
- "name": "spacebar-server",
- "version": "1.0.0",
- "description": "A Spacebar server written in Node.js",
- "scripts": {
- "prepare": "husky install",
- "postinstall": "patch-package",
- "start": "node --enable-source-maps dist/bundle/start.js",
- "start:api": "node --enable-source-maps dist/api/start.js",
- "start:cdn": "node --enable-source-maps dist/cdn/start.js",
- "start:gateway": "node --enable-source-maps dist/gateway/start.js",
- "build": "npm run build:src && npm run generate:schema && npm run generate:openapi",
- "build:src": "tsc -b -v",
- "watch": "tsc -w -b .",
- "test": "node scripts/test.js",
- "lint": "eslint .",
- "setup": "npm run build && npm run generate:schema",
- "sync:db": "npm run build && node scripts/syncronise.js",
- "generate:rights": "node scripts/rights.js",
- "generate:schema": "node scripts/schema.js",
- "generate:migration": "node -r dotenv/config -r module-alias/register node_modules/typeorm/cli.js migration:generate -d dist/util/util/Database.js",
- "generate:openapi": "node scripts/openapi.js",
- "add:license": "node scripts/license.js",
- "migrate-from-staging": "node -r dotenv/config -r module-alias/register scripts/stagingMigration/index.js",
- "node:tests": "npm run build:src && node -r dotenv/config -r module-alias/register --enable-source-maps --test --experimental-test-coverage dist/**/*.test.js"
- },
- "main": "dist/bundle/index.js",
- "types": "src/bundle/index.ts",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/spacebarchat/server.git"
- },
- "author": "Spacebar",
- "license": "AGPL-3.0-only",
- "bugs": {
- "url": "https://github.com/spacebarchat/server/issues"
- },
- "imports": {
- "#*": "./dist/*/index.js"
- },
- "homepage": "https://spacebar.chat",
- "devDependencies": {
- "@eslint/eslintrc": "^3.3.3",
- "@eslint/js": "^9.14.0",
- "@spacebarchat/spacebar-webrtc-types": "^1.0.1",
- "@types/amqplib": "^0.10.8",
- "@types/bcrypt": "^6.0.0",
- "@types/body-parser": "^1.19.6",
- "@types/cookie-parser": "^1.4.10",
- "@types/express": "^5.0.6",
- "@types/i18next-node-fs-backend": "^2.1.5",
- "@types/json-bigint": "^1.0.4",
- "@types/jsonwebtoken": "^9.0.10",
- "@types/module-alias": "^2.0.4",
- "@types/morgan": "^1.9.10",
- "@types/multer": "^2.0.0",
- "@types/murmurhash-js": "^1.0.6",
- "@types/node": "^24.10.4",
- "@types/nodemailer": "^7.0.4",
- "@types/probe-image-size": "^7.2.5",
- "@types/sharp": "^0.31.1",
- "@types/ws": "^8.18.1",
- "@typescript-eslint/eslint-plugin": "^8.50.0",
- "@typescript-eslint/parser": "^8.50.0",
- "eslint": "^9.39.2",
- "globals": "^16.5.0",
- "husky": "^9.1.7",
- "patch-package": "^8.0.1",
- "prettier": "^3.7.4",
- "pretty-quick": "^4.2.2",
- "ts-node": "^10.9.2",
- "typescript": "^5.9.3",
- "typescript-json-schema": "^0.65.1"
- },
- "dependencies": {
- "@aws-sdk/client-s3": "^3.953.0",
- "@toondepauw/node-zstd": "^1.2.0",
- "ajv": "^8.17.1",
- "ajv-formats": "^3.0.1",
- "amqplib": "^0.10.9",
- "bcrypt": "^6.0.0",
- "body-parser": "^2.2.1",
- "chalk": "^5.6.2",
- "cheerio": "^1.1.2",
- "cookie-parser": "^1.4.7",
- "discord-protos": "^1.2.90",
- "dotenv": "^17.2.3",
- "email-providers": "^2.19.0",
- "exif-be-gone": "^1.5.1",
- "express": "^5.2.1",
- "fast-zlib": "^2.0.1",
- "fido2-lib": "^3.5.5",
- "file-type": "^21.1.1",
- "form-data": "^4.0.5",
- "i18next": "^25.7.3",
- "i18next-fs-backend": "^2.6.1",
- "i18next-http-middleware": "^3.9.0",
- "image-size": "^2.0.2",
- "json-bigint": "^1.0.0",
- "jsonwebtoken": "^9.0.3",
- "module-alias": "^2.2.3",
- "morgan": "^1.10.1",
- "ms": "^2.1.3",
- "multer": "^2.0.2",
- "murmurhash-js": "^1.0.0",
- "node-2fa": "^2.0.3",
- "pg": "^8.16.3",
- "pg-query-stream": "^4.10.3",
- "picocolors": "^1.1.1",
- "probe-image-size": "^7.2.3",
- "reflect-metadata": "^0.2.2",
- "sqlite3": "^5.1.7",
- "tslib": "^2.8.1",
- "typeorm": "^0.3.28",
- "wretch": "^2.11.1",
- "ws": "^8.18.3"
- },
- "_moduleAliases": {
- "@spacebar/api": "dist/api",
- "@spacebar/cdn": "dist/cdn",
- "@spacebar/gateway": "dist/gateway",
- "@spacebar/util": "dist/util",
- "@spacebar/webrtc": "dist/webrtc",
- "@spacebar/schemas": "dist/schemas",
- "lambert-server": "dist/util/util/lambert-server"
- },
- "optionalDependencies": {
- "@sendgrid/mail": "^8.1.6",
- "@yukikaze-bot/erlpack": "^1.0.1",
- "jimp": "^1.6.0",
- "mailgun.js": "^12.4.0",
- "node-mailjet": "^6.0.11",
- "nodemailer": "^7.0.11"
- }
+ "name": "spacebar-server",
+ "version": "1.0.0",
+ "description": "A Spacebar server written in Node.js",
+ "scripts": {
+ "prepare": "husky install",
+ "postinstall": "patch-package",
+ "start": "node --enable-source-maps dist/bundle/start.js",
+ "start:api": "node --enable-source-maps dist/api/start.js",
+ "start:cdn": "node --enable-source-maps dist/cdn/start.js",
+ "start:gateway": "node --enable-source-maps dist/gateway/start.js",
+ "build": "npm run build:src && npm run generate:schema && npm run generate:openapi",
+ "build:src": "tsc -b -v",
+ "watch": "tsc -w -b .",
+ "test": "node scripts/test.js",
+ "lint": "eslint .",
+ "setup": "npm run build && npm run generate:schema",
+ "sync:db": "npm run build && node scripts/syncronise.js",
+ "generate:rights": "node scripts/rights.js",
+ "generate:schema": "node scripts/schema.js",
+ "generate:migration": "node -r dotenv/config -r module-alias/register node_modules/typeorm/cli.js migration:generate -d dist/util/util/Database.js",
+ "generate:openapi": "node scripts/openapi.js",
+ "add:license": "node scripts/license.js",
+ "migrate-from-staging": "node -r dotenv/config -r module-alias/register scripts/stagingMigration/index.js",
+ "node:tests": "npm run build:src && node -r dotenv/config -r module-alias/register --enable-source-maps --test --experimental-test-coverage dist/**/*.test.js"
+ },
+ "main": "dist/bundle/index.js",
+ "types": "src/bundle/index.ts",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/spacebarchat/server.git"
+ },
+ "author": "Spacebar",
+ "license": "AGPL-3.0-only",
+ "bugs": {
+ "url": "https://github.com/spacebarchat/server/issues"
+ },
+ "imports": {
+ "#*": "./dist/*/index.js"
+ },
+ "homepage": "https://spacebar.chat",
+ "devDependencies": {
+ "@eslint/eslintrc": "^3.3.3",
+ "@eslint/js": "^9.14.0",
+ "@spacebarchat/spacebar-webrtc-types": "^1.0.1",
+ "@types/amqplib": "^0.10.8",
+ "@types/bcrypt": "^6.0.0",
+ "@types/body-parser": "^1.19.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/express": "^5.0.6",
+ "@types/i18next-node-fs-backend": "^2.1.5",
+ "@types/json-bigint": "^1.0.4",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/module-alias": "^2.0.4",
+ "@types/morgan": "^1.9.10",
+ "@types/multer": "^2.0.0",
+ "@types/murmurhash-js": "^1.0.6",
+ "@types/node": "^24.10.4",
+ "@types/nodemailer": "^7.0.4",
+ "@types/probe-image-size": "^7.2.5",
+ "@types/sharp": "^0.31.1",
+ "@types/ws": "^8.18.1",
+ "@typescript-eslint/eslint-plugin": "^8.50.0",
+ "@typescript-eslint/parser": "^8.50.0",
+ "eslint": "^9.39.2",
+ "globals": "^16.5.0",
+ "husky": "^9.1.7",
+ "patch-package": "^8.0.1",
+ "prettier": "^3.7.4",
+ "pretty-quick": "^4.2.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.9.3",
+ "typescript-json-schema": "^0.65.1"
+ },
+ "dependencies": {
+ "@aws-sdk/client-s3": "^3.953.0",
+ "@toondepauw/node-zstd": "^1.2.0",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "amqplib": "^0.10.9",
+ "bcrypt": "^6.0.0",
+ "body-parser": "^2.2.1",
+ "chalk": "^5.6.2",
+ "cheerio": "^1.1.2",
+ "cookie-parser": "^1.4.7",
+ "discord-protos": "^1.2.90",
+ "dotenv": "^17.2.3",
+ "email-providers": "^2.19.0",
+ "exif-be-gone": "^1.5.1",
+ "express": "^5.2.1",
+ "fast-zlib": "^2.0.1",
+ "fido2-lib": "^3.5.5",
+ "file-type": "^21.1.1",
+ "form-data": "^4.0.5",
+ "i18next": "^25.7.3",
+ "i18next-fs-backend": "^2.6.1",
+ "i18next-http-middleware": "^3.9.0",
+ "image-size": "^2.0.2",
+ "json-bigint": "^1.0.0",
+ "jsonwebtoken": "^9.0.3",
+ "module-alias": "^2.2.3",
+ "morgan": "^1.10.1",
+ "ms": "^2.1.3",
+ "multer": "^2.0.2",
+ "murmurhash-js": "^1.0.0",
+ "node-2fa": "^2.0.3",
+ "pg": "^8.16.3",
+ "pg-query-stream": "^4.10.3",
+ "picocolors": "^1.1.1",
+ "probe-image-size": "^7.2.3",
+ "reflect-metadata": "^0.2.2",
+ "sqlite3": "^5.1.7",
+ "tslib": "^2.8.1",
+ "typeorm": "^0.3.28",
+ "wretch": "^2.11.1",
+ "ws": "^8.18.3"
+ },
+ "_moduleAliases": {
+ "@spacebar/api": "dist/api",
+ "@spacebar/cdn": "dist/cdn",
+ "@spacebar/gateway": "dist/gateway",
+ "@spacebar/util": "dist/util",
+ "@spacebar/webrtc": "dist/webrtc",
+ "@spacebar/schemas": "dist/schemas",
+ "lambert-server": "dist/util/util/lambert-server"
+ },
+ "optionalDependencies": {
+ "@sendgrid/mail": "^8.1.6",
+ "@yukikaze-bot/erlpack": "^1.0.1",
+ "jimp": "^1.6.0",
+ "mailgun.js": "^12.4.0",
+ "node-mailjet": "^6.0.11",
+ "nodemailer": "^7.0.11"
+ }
}
diff --git a/scripts/genIndex.js b/scripts/genIndex.js
index c1b682274..e4601f3ef 100644
--- a/scripts/genIndex.js
+++ b/scripts/genIndex.js
@@ -41,29 +41,29 @@ let content = `/*
// node scripts/genIndex.js /path/to/dir
const targetDir = process.argv[2];
if (!targetDir) {
- console.error("Please provide a target directory.");
- process.exit(1);
+ console.error("Please provide a target directory.");
+ process.exit(1);
}
if (fs.existsSync(path.join(targetDir, "index.js")) || fs.existsSync(path.join(targetDir, "index.ts"))) {
- console.error("index.js or index.ts already exists in the target directory.");
- process.exit(1);
+ console.error("index.js or index.ts already exists in the target directory.");
+ process.exit(1);
}
const dirs = fs.readdirSync(targetDir).filter((f) => fs.statSync(path.join(targetDir, f)).isDirectory());
for (const dir of dirs) {
- content += `export * from "./${dir}";\n`;
+ content += `export * from "./${dir}";\n`;
}
const files = fs.readdirSync(targetDir).filter((f) => f.endsWith(".js") || f.endsWith(".ts"));
for (const file of files) {
- const filePath = path.join(targetDir, file);
- const stat = fs.statSync(filePath);
- if (stat.isFile()) {
- const ext = path.extname(file);
- const base = path.basename(file, ext);
- content += `export * from "./${base}";\n`;
- }
+ const filePath = path.join(targetDir, file);
+ const stat = fs.statSync(filePath);
+ if (stat.isFile()) {
+ const ext = path.extname(file);
+ const base = path.basename(file, ext);
+ content += `export * from "./${base}";\n`;
+ }
}
fs.writeFileSync(path.join(targetDir, "index.ts"), content);
diff --git a/scripts/license.js b/scripts/license.js
index 380a3ed6f..0cc9b0946 100644
--- a/scripts/license.js
+++ b/scripts/license.js
@@ -12,43 +12,43 @@ const walk = require("./util/walk");
const SPACEBAR_SOURCE_DIR = Path.join(__dirname, "..", "src");
const SPACEBAR_SCRIPTS_DIR = Path.join(__dirname);
const SPACEBAR_LICENSE_PREAMBLE = fs
- .readFileSync(Path.join(__dirname, "util", "licensePreamble.txt"))
- .toString()
- .split("\r") // remove windows bs
- .join("") // ^
- .split("\n")
- .map((x) => `\t${x}`)
- .join("\n");
+ .readFileSync(Path.join(__dirname, "util", "licensePreamble.txt"))
+ .toString()
+ .split("\r") // remove windows bs
+ .join("") // ^
+ .split("\n")
+ .map((x) => `\t${x}`)
+ .join("\n");
const languageCommentStrings = {
- js: ["/*", "*/"],
- ts: ["/*", "*/"],
+ js: ["/*", "*/"],
+ ts: ["/*", "*/"],
};
const addToDir = (dir) => {
- const files = walk(dir, Object.keys(languageCommentStrings));
+ const files = walk(dir, Object.keys(languageCommentStrings));
- for (let path of files) {
- const file = fs.readFileSync(path).toString().split("\r").join("");
- const fileType = path.slice(path.lastIndexOf(".") + 1);
- const commentStrings = languageCommentStrings[fileType];
- if (!commentStrings) continue;
+ for (let path of files) {
+ const file = fs.readFileSync(path).toString().split("\r").join("");
+ const fileType = path.slice(path.lastIndexOf(".") + 1);
+ const commentStrings = languageCommentStrings[fileType];
+ if (!commentStrings) continue;
- const preamble = commentStrings[0] + "\n" + SPACEBAR_LICENSE_PREAMBLE + "\n" + commentStrings[1];
+ const preamble = commentStrings[0] + "\n" + SPACEBAR_LICENSE_PREAMBLE + "\n" + commentStrings[1];
- if (file.startsWith(preamble)) {
- continue;
- }
+ if (file.startsWith(preamble)) {
+ continue;
+ }
- // This is kind of lame.
- if (file.includes("@fc-license-skip") && path != __filename) {
- console.log(`skipping ${path} as it has a different license.`);
- continue;
- }
+ // This is kind of lame.
+ if (file.includes("@fc-license-skip") && path != __filename) {
+ console.log(`skipping ${path} as it has a different license.`);
+ continue;
+ }
- console.log(`writing to ${path}`);
- fs.writeFileSync(path, preamble + "\n\n" + file);
- }
+ console.log(`writing to ${path}`);
+ fs.writeFileSync(path, preamble + "\n\n" + file);
+ }
};
addToDir(SPACEBAR_SOURCE_DIR);
diff --git a/scripts/openapi.js b/scripts/openapi.js
index 39396d8d0..5afee152a 100644
--- a/scripts/openapi.js
+++ b/scripts/openapi.js
@@ -32,227 +32,227 @@ const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
let specification = {
- openapi: "3.1.0",
- info: {
- title: "Spacebar Server",
- description:
- "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.",
- license: {
- name: "AGPLV3",
- url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
- },
- version: "1.0.0",
- },
- externalDocs: {
- description: "Spacebar Docs",
- url: "https://docs.spacebar.chat",
- },
- servers: [
- {
- url: "https://old.server.spacebar.chat/api/",
- description: "Official Spacebar Instance",
- },
- ],
- components: {
- securitySchemes: {
- bearer: {
- type: "http",
- scheme: "bearer",
- description: "Bearer/Bot prefixes are not required.",
- bearerFormat: "JWT",
- in: "header",
- },
- },
- },
- tags: [],
- paths: {},
+ openapi: "3.1.0",
+ info: {
+ title: "Spacebar Server",
+ description:
+ "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.",
+ license: {
+ name: "AGPLV3",
+ url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
+ },
+ version: "1.0.0",
+ },
+ externalDocs: {
+ description: "Spacebar Docs",
+ url: "https://docs.spacebar.chat",
+ },
+ servers: [
+ {
+ url: "https://old.server.spacebar.chat/api/",
+ description: "Official Spacebar Instance",
+ },
+ ],
+ components: {
+ securitySchemes: {
+ bearer: {
+ type: "http",
+ scheme: "bearer",
+ description: "Bearer/Bot prefixes are not required.",
+ bearerFormat: "JWT",
+ in: "header",
+ },
+ },
+ },
+ tags: [],
+ paths: {},
};
const schemaRegEx = new RegExp(/^[\w.]+$/);
function combineSchemas(schemas) {
- let definitions = {};
+ let definitions = {};
- for (const name in schemas) {
- definitions = {
- ...definitions,
- ...schemas[name].definitions,
- [name]: {
- ...schemas[name],
- definitions: undefined,
- $schema: undefined,
- },
- };
- }
+ for (const name in schemas) {
+ definitions = {
+ ...definitions,
+ ...schemas[name].definitions,
+ [name]: {
+ ...schemas[name],
+ definitions: undefined,
+ $schema: undefined,
+ },
+ };
+ }
- for (const key in definitions) {
- if (!schemaRegEx.test(key)) {
- console.error(` \x1b[5m${bgRedBright("ERROR")}\x1b[25m Invalid schema name: ${key}, context:`, definitions[key]);
- continue;
- }
- specification.components = specification.components || {};
- specification.components.schemas = specification.components.schemas || {};
- specification.components.schemas[key] = definitions[key];
- delete definitions[key].additionalProperties;
- delete definitions[key].$schema;
- const definition = definitions[key];
+ for (const key in definitions) {
+ if (!schemaRegEx.test(key)) {
+ console.error(` \x1b[5m${bgRedBright("ERROR")}\x1b[25m Invalid schema name: ${key}, context:`, definitions[key]);
+ continue;
+ }
+ specification.components = specification.components || {};
+ specification.components.schemas = specification.components.schemas || {};
+ 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;
- }
- }
- }
- }
- }
+ 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;
+ return definitions;
}
function getTag(key) {
- return key.match(/\/([\w-]+)/)[1];
+ return key.match(/\/([\w-]+)/)[1];
}
function apiRoutes(missingRoutes) {
- const routes = getRouteDescriptions();
+ const routes = getRouteDescriptions();
- // populate tags
- const tags = Array.from(routes.keys())
- .map((x) => getTag(x))
- .sort((a, b) => a.localeCompare(b));
- specification.tags = [...new Set(tags)].map((x) => ({ name: x }));
+ // populate tags
+ const tags = Array.from(routes.keys())
+ .map((x) => getTag(x))
+ .sort((a, b) => a.localeCompare(b));
+ specification.tags = [...new Set(tags)].map((x) => ({ name: x }));
- routes.forEach((route, pathAndMethod) => {
- const [p, method] = pathAndMethod.split("|");
- const path = p.replace(/:(\w+)/g, "{$1}");
+ routes.forEach((route, pathAndMethod) => {
+ const [p, method] = pathAndMethod.split("|");
+ const path = p.replace(/:(\w+)/g, "{$1}");
- let obj = specification.paths[path]?.[method] || {};
- obj["x-right-required"] = route.right;
- obj["x-permission-required"] = route.permission;
- obj["x-fires-event"] = route.event;
+ let obj = specification.paths[path]?.[method] || {};
+ obj["x-right-required"] = route.right;
+ obj["x-permission-required"] = route.permission;
+ obj["x-fires-event"] = route.event;
- if (
- !NO_AUTHORIZATION_ROUTES.some((x) => {
- if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x);
- return x.test(method.toUpperCase() + " " + path);
- })
- ) {
- obj.security = [{ bearer: [] }];
- }
+ if (
+ !NO_AUTHORIZATION_ROUTES.some((x) => {
+ if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x);
+ return x.test(method.toUpperCase() + " " + path);
+ })
+ ) {
+ obj.security = [{ bearer: [] }];
+ }
- if (route.description) obj.description = route.description;
- if (route.summary) obj.summary = route.summary;
- if (route.deprecated) obj.deprecated = route.deprecated;
+ if (route.description) obj.description = route.description;
+ if (route.summary) obj.summary = route.summary;
+ if (route.deprecated) obj.deprecated = route.deprecated;
- if (route.requestBody) {
- obj.requestBody = {
- required: true,
- content: {
- "application/json": {
- schema: {
- $ref: `#/components/schemas/${route.requestBody}`,
- },
- },
- },
- };
- }
+ if (route.requestBody) {
+ obj.requestBody = {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ $ref: `#/components/schemas/${route.requestBody}`,
+ },
+ },
+ },
+ };
+ }
- if (route.responses) {
- obj.responses = {};
+ if (route.responses) {
+ obj.responses = {};
- for (const [k, v] of Object.entries(route.responses)) {
- if (v.body)
- obj.responses[k] = {
- description: obj?.responses?.[k]?.description || "",
- content: {
- "application/json": {
- schema: {
- $ref: `#/components/schemas/${v.body}`,
- },
- },
- },
- };
- else
- obj.responses[k] = {
- description: obj?.responses?.[k]?.description || "No description available",
- };
- }
- } else {
- obj.responses = {
- default: {
- description: "No description available",
- },
- };
- }
+ for (const [k, v] of Object.entries(route.responses)) {
+ if (v.body)
+ obj.responses[k] = {
+ description: obj?.responses?.[k]?.description || "",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: `#/components/schemas/${v.body}`,
+ },
+ },
+ },
+ };
+ else
+ obj.responses[k] = {
+ description: obj?.responses?.[k]?.description || "No description available",
+ };
+ }
+ } else {
+ obj.responses = {
+ default: {
+ description: "No description available",
+ },
+ };
+ }
- // handles path parameters
- if (p.includes(":")) {
- obj.parameters = p.match(/:\w+/g)?.map((x) => ({
- name: x.replace(":", ""),
- in: "path",
- required: true,
- schema: { type: "string" },
- description: x.replace(":", ""),
- }));
- }
+ // handles path parameters
+ if (p.includes(":")) {
+ obj.parameters = p.match(/:\w+/g)?.map((x) => ({
+ name: x.replace(":", ""),
+ in: "path",
+ required: true,
+ schema: { type: "string" },
+ description: x.replace(":", ""),
+ }));
+ }
- if (route.query) {
- // map to array
- const query = Object.entries(route.query).map(([k, v]) => ({
- name: k,
- in: "query",
- required: v.required,
- schema: { type: v.type },
- description: v.description,
- }));
+ if (route.query) {
+ // map to array
+ const query = Object.entries(route.query).map(([k, v]) => ({
+ name: k,
+ in: "query",
+ required: v.required,
+ schema: { type: v.type },
+ description: v.description,
+ }));
- obj.parameters = [...(obj.parameters || []), ...query];
- }
+ obj.parameters = [...(obj.parameters || []), ...query];
+ }
- obj.tags = [...new Set([...(obj.tags || []), getTag(p)])];
+ obj.tags = [...new Set([...(obj.tags || []), getTag(p)])];
- if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
- obj["x-badges"] = [
- {
- label: "Spacebar-only",
- color: "red",
- },
- ];
- }
+ if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
+ obj["x-badges"] = [
+ {
+ label: "Spacebar-only",
+ color: "red",
+ },
+ ];
+ }
- specification.paths[path] = Object.assign(specification.paths[path] || {}, {
- [method]: obj,
- });
- });
+ specification.paths[path] = Object.assign(specification.paths[path] || {}, {
+ [method]: obj,
+ });
+ });
}
async function main() {
- console.log("Generating OpenAPI Specification...");
+ console.log("Generating OpenAPI Specification...");
- const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", {
- headers: {
- Accept: "application/json",
- },
- });
- const missingRoutes = await routesRes.json();
+ const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", {
+ headers: {
+ Accept: "application/json",
+ },
+ });
+ const missingRoutes = await routesRes.json();
- combineSchemas(schemas);
- apiRoutes(missingRoutes);
+ combineSchemas(schemas);
+ apiRoutes(missingRoutes);
- fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number"));
- console.log("Wrote OpenAPI specification to", openapiPath);
- const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds);
- console.log(
- "Specification contains",
- Object.keys(specification.paths).length,
- "paths and",
- Object.keys(specification.components.schemas).length,
- "schemas in",
- elapsedMs,
- "ms.",
- );
+ fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number"));
+ console.log("Wrote OpenAPI specification to", openapiPath);
+ const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds);
+ console.log(
+ "Specification contains",
+ Object.keys(specification.paths).length,
+ "paths and",
+ Object.keys(specification.components.schemas).length,
+ "schemas in",
+ elapsedMs,
+ "ms.",
+ );
}
main();
diff --git a/scripts/schema.js b/scripts/schema.js
index 1779c8d6d..67c5563c3 100644
--- a/scripts/schema.js
+++ b/scripts/schema.js
@@ -24,10 +24,10 @@ const totalSw = Stopwatch.startNew();
const conWarn = console.warn;
console.warn = (...args) => {
- // silence some expected warnings
- if (args[0] === "initializer is expression for property id") return;
- if (args[0].startsWith("unknown initializer for property ") && args[0].endsWith("[object Object]")) return;
- conWarn(...args);
+ // silence some expected warnings
+ if (args[0] === "initializer is expression for property id") return;
+ if (args[0].startsWith("unknown initializer for property ") && args[0].endsWith("[object Object]")) return;
+ conWarn(...args);
};
const path = require("path");
@@ -41,27 +41,27 @@ const exclusionList = JSON.parse(fs.readFileSync(path.join(__dirname, "schemaExc
// @type {TJS.PartialArgs}
const settings = {
- required: true,
- ignoreErrors: true,
- excludePrivate: true,
- defaultNumberType: "integer",
- noExtraProps: true,
- defaultProps: false,
- useTypeOfKeyword: true, // should help catch functions?
+ required: true,
+ ignoreErrors: true,
+ excludePrivate: true,
+ defaultNumberType: "integer",
+ noExtraProps: true,
+ defaultProps: false,
+ useTypeOfKeyword: true, // should help catch functions?
};
const baseClassProperties = [
- // BaseClass methods
- "toJSON",
- "hasId",
- "save",
- "remove",
- "softRemove",
- "recover",
- "reload",
- "assign",
- "_do_validate", // ?
- "hasId", // ?
+ // BaseClass methods
+ "toJSON",
+ "hasId",
+ "save",
+ "remove",
+ "softRemove",
+ "recover",
+ "reload",
+ "assign",
+ "_do_validate", // ?
+ "hasId", // ?
];
const ExcludeAndWarn = [...exclusionList.manualWarn, ...exclusionList.manualWarnRe.map((r) => new RegExp(r))];
@@ -69,315 +69,315 @@ const Excluded = [...exclusionList.manual, ...exclusionList.manualRe.map((r) =>
const Included = [...exclusionList.include, ...exclusionList.includeRe.map((r) => new RegExp(r))];
const excludedLambdas = [
- (n, s) => {
- // attempt to import
- if (JSON.stringify(s).includes(`#/definitions/import(`)) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it attempted to use import().`);
- exclusionList.auto.push({ value: n, reason: "Uses import()" });
- return true;
- }
- },
- (n, s) => {
- if (JSON.stringify(s).includes(process.cwd())) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked $PWD.`);
- exclusionList.auto.push({ value: n, reason: "Leaked $PWD" });
- return true;
- }
- },
- (n, s) => {
- if (JSON.stringify(s).includes(process.env.HOME)) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked a $HOME path.`);
- exclusionList.auto.push({ value: n, reason: "Leaked $HOME" });
- return true;
- }
- },
- (n, s) => {
- if (s["$ref"] === `#/definitions/${n}`) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it is a self-reference only schema.`);
- exclusionList.auto.push({ value: n, reason: "Self-reference only schema" });
- // fs.writeFileSync(`fucked/${n}.json`, JSON.stringify(s, null, 4));
- return true;
- }
- },
- (n, s) => {
- if (s.description?.match(/Smithy/)) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it appears to be an AWS Smithy schema.`);
- exclusionList.auto.push({ value: n, reason: "AWS Smithy schema" });
- return true;
- }
- },
- (n, s) => {
- if (s.description?.startsWith("
")) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as we don't use HTML paragraphs for descriptions.`);
- exclusionList.auto.push({ value: n, reason: "HTML paragraph in description" });
- return true;
- }
- },
- (n, s) => {
- if (s.properties && Object.keys(s.properties).every((x) => x[0] === x[0].toUpperCase())) {
- console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as all its properties have uppercase characters.`);
- exclusionList.auto.push({ value: n, reason: "Schema with only uppercase properties" });
- return true;
- }
- },
- // (n, s) => {
- // if (JSON.stringify(s).length <= 300) {
- // console.log({n, s});
- // }
- // }
+ (n, s) => {
+ // attempt to import
+ if (JSON.stringify(s).includes(`#/definitions/import(`)) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it attempted to use import().`);
+ exclusionList.auto.push({ value: n, reason: "Uses import()" });
+ return true;
+ }
+ },
+ (n, s) => {
+ if (JSON.stringify(s).includes(process.cwd())) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked $PWD.`);
+ exclusionList.auto.push({ value: n, reason: "Leaked $PWD" });
+ return true;
+ }
+ },
+ (n, s) => {
+ if (JSON.stringify(s).includes(process.env.HOME)) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked a $HOME path.`);
+ exclusionList.auto.push({ value: n, reason: "Leaked $HOME" });
+ return true;
+ }
+ },
+ (n, s) => {
+ if (s["$ref"] === `#/definitions/${n}`) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it is a self-reference only schema.`);
+ exclusionList.auto.push({ value: n, reason: "Self-reference only schema" });
+ // fs.writeFileSync(`fucked/${n}.json`, JSON.stringify(s, null, 4));
+ return true;
+ }
+ },
+ (n, s) => {
+ if (s.description?.match(/Smithy/)) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it appears to be an AWS Smithy schema.`);
+ exclusionList.auto.push({ value: n, reason: "AWS Smithy schema" });
+ return true;
+ }
+ },
+ (n, s) => {
+ if (s.description?.startsWith("
")) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as we don't use HTML paragraphs for descriptions.`);
+ exclusionList.auto.push({ value: n, reason: "HTML paragraph in description" });
+ return true;
+ }
+ },
+ (n, s) => {
+ if (s.properties && Object.keys(s.properties).every((x) => x[0] === x[0].toUpperCase())) {
+ console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as all its properties have uppercase characters.`);
+ exclusionList.auto.push({ value: n, reason: "Schema with only uppercase properties" });
+ return true;
+ }
+ },
+ // (n, s) => {
+ // if (JSON.stringify(s).length <= 300) {
+ // console.log({n, s});
+ // }
+ // }
];
function includesMatch(haystack, needles, log = false) {
- for (const needle of needles) {
- const match = needle instanceof RegExp ? needle.test(haystack) : haystack === needle;
- if (match) {
- if (log) console.warn(redBright("[WARN]:"), "Excluding schema", haystack, "due to match with", needle);
- return needle;
- }
- }
- return null;
+ for (const needle of needles) {
+ const match = needle instanceof RegExp ? needle.test(haystack) : haystack === needle;
+ if (match) {
+ if (log) console.warn(redBright("[WARN]:"), "Excluding schema", haystack, "due to match with", needle);
+ return needle;
+ }
+ }
+ return null;
}
async function main() {
- const stepSw = Stopwatch.startNew();
+ const stepSw = Stopwatch.startNew();
- process.stdout.write("Loading program... ");
- const program = TJS.programFromConfig(path.join(__dirname, "..", "tsconfig.json"), walk(path.join(__dirname, "..", "src", "schemas")));
- const generator = TJS.buildGenerator(program, settings);
- if (!generator || !program) {
- console.log(redBright("Failed to create schema generator."));
- return;
- }
+ process.stdout.write("Loading program... ");
+ const program = TJS.programFromConfig(path.join(__dirname, "..", "tsconfig.json"), walk(path.join(__dirname, "..", "src", "schemas")));
+ const generator = TJS.buildGenerator(program, settings);
+ if (!generator || !program) {
+ console.log(redBright("Failed to create schema generator."));
+ return;
+ }
- const elapsedLoad = stepSw.getElapsedAndReset();
- process.stdout.write("Done in " + yellowBright(elapsedLoad.totalMilliseconds + "." + elapsedLoad.microseconds) + " ms\n");
+ const elapsedLoad = stepSw.getElapsedAndReset();
+ process.stdout.write("Done in " + yellowBright(elapsedLoad.totalMilliseconds + "." + elapsedLoad.microseconds) + " ms\n");
- process.stdout.write("Generating schema list... ");
- let schemas = generator.getUserSymbols().filter((x) => {
- return (
- (x.endsWith("Schema") || x.endsWith("Response") || x.startsWith("API")) &&
- // !ExcludeAndWarn.some((exc) => {
- // const match = exc instanceof RegExp ? exc.test(x) : x === exc;
- // if (match) console.warn("Warning: Excluding schema", x);
- // return match;
- // }) &&
- // !Excluded.some((exc) => (exc instanceof RegExp ? exc.test(x) : x === exc))
- (includesMatch(x, Included) || (!includesMatch(x, ExcludeAndWarn, true) && !includesMatch(x, Excluded)))
- );
- });
- //.sort((a,b) => a.localeCompare(b));
+ process.stdout.write("Generating schema list... ");
+ let schemas = generator.getUserSymbols().filter((x) => {
+ return (
+ (x.endsWith("Schema") || x.endsWith("Response") || x.startsWith("API")) &&
+ // !ExcludeAndWarn.some((exc) => {
+ // const match = exc instanceof RegExp ? exc.test(x) : x === exc;
+ // if (match) console.warn("Warning: Excluding schema", x);
+ // return match;
+ // }) &&
+ // !Excluded.some((exc) => (exc instanceof RegExp ? exc.test(x) : x === exc))
+ (includesMatch(x, Included) || (!includesMatch(x, ExcludeAndWarn, true) && !includesMatch(x, Excluded)))
+ );
+ });
+ //.sort((a,b) => a.localeCompare(b));
- const elapsedList = stepSw.getElapsedAndReset();
- process.stdout.write("Done in " + yellowBright(elapsedList.totalMilliseconds + "." + elapsedList.microseconds) + " ms\n");
- console.log("Found", yellowBright(schemas.length), "schemas to process.");
+ const elapsedList = stepSw.getElapsedAndReset();
+ process.stdout.write("Done in " + yellowBright(elapsedList.totalMilliseconds + "." + elapsedList.microseconds) + " ms\n");
+ console.log("Found", yellowBright(schemas.length), "schemas to process.");
- let definitions = {};
- let nestedDefinitions = {};
- let writePromises = [];
+ let definitions = {};
+ let nestedDefinitions = {};
+ let writePromises = [];
- if (process.env.WRITE_SCHEMA_DIR === "true") {
- fs.rmSync("schemas_orig", { recursive: true, force: true });
- fs.mkdirSync("schemas_orig");
+ if (process.env.WRITE_SCHEMA_DIR === "true") {
+ fs.rmSync("schemas_orig", { recursive: true, force: true });
+ fs.mkdirSync("schemas_orig");
- fs.rmSync("schemas_nested", { recursive: true, force: true });
- fs.mkdirSync("schemas_nested");
+ fs.rmSync("schemas_nested", { recursive: true, force: true });
+ fs.mkdirSync("schemas_nested");
- fs.rmSync("schemas_final", { recursive: true, force: true });
- fs.mkdirSync("schemas_final");
- }
+ fs.rmSync("schemas_final", { recursive: true, force: true });
+ fs.mkdirSync("schemas_final");
+ }
- const schemaSw = Stopwatch.startNew();
- for (const name of schemas) {
- process.stdout.write(`Processing schema ${name}... `);
- const part = TJS.generateSchema(program, name, settings, [], generator);
- if (!part) continue;
+ const schemaSw = Stopwatch.startNew();
+ for (const name of schemas) {
+ process.stdout.write(`Processing schema ${name}... `);
+ const part = TJS.generateSchema(program, name, settings, [], generator);
+ if (!part) continue;
- if (definitions[name]) {
- process.stdout.write(yellow(` [ERROR] Duplicate schema name detected: ${name}. Overwriting previous schema.`));
- }
+ if (definitions[name]) {
+ process.stdout.write(yellow(` [ERROR] Duplicate schema name detected: ${name}. Overwriting previous schema.`));
+ }
- if (!includesMatch(name, Included) && excludedLambdas.some((fn) => fn(name, part))) {
- continue;
- }
+ if (!includesMatch(name, Included) && excludedLambdas.some((fn) => fn(name, part))) {
+ continue;
+ }
- if (process.env.WRITE_SCHEMA_DIR === "true") writePromises.push(async () => await fsp.writeFile(path.join("schemas_orig", `${name}.json`), JSON.stringify(part, null, 4)));
+ if (process.env.WRITE_SCHEMA_DIR === "true") writePromises.push(async () => await fsp.writeFile(path.join("schemas_orig", `${name}.json`), JSON.stringify(part, null, 4)));
- // testing:
- function mergeDefs(schemaName, schema) {
- if (schema.definitions) {
- // schema["x-sb-defs"] = Object.keys(schema.definitions);
- process.stdout.write(cyanBright("Processing nested... "));
- for (const defKey in schema.definitions) {
- if (definitions[defKey] && deepEqual(definitions[defKey], schema.definitions[defKey])) {
- // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping.");
- schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey));
- process.stdout.write(greenBright("T"));
- } else if (!nestedDefinitions[defKey]) {
- nestedDefinitions[defKey] = schema.definitions[defKey];
- schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey));
- // console.log("Tracking sub-definition", defKey, "from schema", schemaName);
- process.stdout.write(green("N"));
- } else if (!deepEqual(nestedDefinitions[defKey], schema.definitions[defKey])) {
- console.log(redBright("[ERROR]"), "Conflicting nested definition for", defKey, "found in schema", schemaName);
- console.log(columnizedObjectDiff(nestedDefinitions[defKey], schema.definitions[defKey], true));
- } else {
- // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping.");
- schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey));
- process.stdout.write(greenBright("M"));
- }
- }
- if (Object.keys(schema.definitions).length === 0) {
- process.stdout.write(greenBright("✓ "));
- delete schema.definitions;
- } else {
- console.log("Remaining definitions in schema", schemaName, "after merge:", Object.keys(schema.definitions));
- }
- }
- }
- mergeDefs(name, part);
+ // testing:
+ function mergeDefs(schemaName, schema) {
+ if (schema.definitions) {
+ // schema["x-sb-defs"] = Object.keys(schema.definitions);
+ process.stdout.write(cyanBright("Processing nested... "));
+ for (const defKey in schema.definitions) {
+ if (definitions[defKey] && deepEqual(definitions[defKey], schema.definitions[defKey])) {
+ // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping.");
+ schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey));
+ process.stdout.write(greenBright("T"));
+ } else if (!nestedDefinitions[defKey]) {
+ nestedDefinitions[defKey] = schema.definitions[defKey];
+ schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey));
+ // console.log("Tracking sub-definition", defKey, "from schema", schemaName);
+ process.stdout.write(green("N"));
+ } else if (!deepEqual(nestedDefinitions[defKey], schema.definitions[defKey])) {
+ console.log(redBright("[ERROR]"), "Conflicting nested definition for", defKey, "found in schema", schemaName);
+ console.log(columnizedObjectDiff(nestedDefinitions[defKey], schema.definitions[defKey], true));
+ } else {
+ // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping.");
+ schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey));
+ process.stdout.write(greenBright("M"));
+ }
+ }
+ if (Object.keys(schema.definitions).length === 0) {
+ process.stdout.write(greenBright("✓ "));
+ delete schema.definitions;
+ } else {
+ console.log("Remaining definitions in schema", schemaName, "after merge:", Object.keys(schema.definitions));
+ }
+ }
+ }
+ mergeDefs(name, part);
- const elapsed = schemaSw.getElapsedAndReset();
- process.stdout.write(
- "Done in " + yellowBright(elapsed.totalMilliseconds + "." + elapsed.microseconds) + " ms, " + yellowBright(JSON.stringify(part).length) + " bytes (unformatted) ",
- );
- if (elapsed.totalMilliseconds >= 20) console.log(bgRedBright("\x1b[5m[SLOW]\x1b[25m"));
- else console.log();
+ const elapsed = schemaSw.getElapsedAndReset();
+ process.stdout.write(
+ "Done in " + yellowBright(elapsed.totalMilliseconds + "." + elapsed.microseconds) + " ms, " + yellowBright(JSON.stringify(part).length) + " bytes (unformatted) ",
+ );
+ if (elapsed.totalMilliseconds >= 20) console.log(bgRedBright("\x1b[5m[SLOW]\x1b[25m"));
+ else console.log();
- definitions = { ...definitions, [name]: { ...part } };
- }
- console.log("Processed", Object.keys(definitions).length, "schemas in", Number(stepSw.elapsed().totalMilliseconds + "." + stepSw.elapsed().microseconds), "ms.");
+ definitions = { ...definitions, [name]: { ...part } };
+ }
+ console.log("Processed", Object.keys(definitions).length, "schemas in", Number(stepSw.elapsed().totalMilliseconds + "." + stepSw.elapsed().microseconds), "ms.");
- console.log("Merging nested definitions into main definitions...");
- let isNewLine = true;
- for (const defKey in nestedDefinitions) {
- if (!includesMatch(defKey, Included, false)) {
- const bannedMatch = includesMatch(defKey, ExcludeAndWarn, false) ?? includesMatch(defKey, Excluded, false);
- if (bannedMatch !== null) {
- // console.log(yellowBright("\n[WARN]"), "Skipping nested definition", defKey, "as it matched a banned format.");
- console.log((isNewLine ? "" : "\n") + redBright("WARNING") + " Excluding schema " + yellowBright(defKey) + " due to match with " + redBright(bannedMatch));
- isNewLine = true;
- continue;
- }
- }
+ console.log("Merging nested definitions into main definitions...");
+ let isNewLine = true;
+ for (const defKey in nestedDefinitions) {
+ if (!includesMatch(defKey, Included, false)) {
+ const bannedMatch = includesMatch(defKey, ExcludeAndWarn, false) ?? includesMatch(defKey, Excluded, false);
+ if (bannedMatch !== null) {
+ // console.log(yellowBright("\n[WARN]"), "Skipping nested definition", defKey, "as it matched a banned format.");
+ console.log((isNewLine ? "" : "\n") + redBright("WARNING") + " Excluding schema " + yellowBright(defKey) + " due to match with " + redBright(bannedMatch));
+ isNewLine = true;
+ continue;
+ }
+ }
- nestedDefinitions[defKey]["$schema"] = "http://json-schema.org/draft-07/schema#";
- if (definitions[defKey]) {
- if (!deepEqual(definitions[defKey], nestedDefinitions[defKey])) {
- if (Object.keys(definitions[defKey]).every((k) => k === "$ref" || k === "$schema")) {
- definitions[defKey] = nestedDefinitions[defKey];
- console.log(yellowBright("\nWARNING"), "Overwriting definition for", defKey, "with nested definition (ref/schema only).");
- isNewLine = true;
- } else {
- console.log(redBright("\nERROR"), "Conflicting definition for", defKey, "found in main definitions.");
- console.log(columnizedObjectDiff(definitions[defKey], nestedDefinitions[defKey], true));
- console.log("Keys:", Object.keys(definitions[defKey]), Object.keys(nestedDefinitions[defKey]));
- isNewLine = true;
- }
- } else {
- // console.log("Definition", defKey, "is identical to existing definition, skipping.");
- }
- } else {
- definitions[defKey] = nestedDefinitions[defKey];
- if (isNewLine) {
- process.stdout.write("Adding nested definitions to main definitions: ");
- isNewLine = false;
- } else process.stdout.write("\x1b[4D, ");
- process.stdout.write(yellowBright(defKey) + "... ");
- }
- }
+ nestedDefinitions[defKey]["$schema"] = "http://json-schema.org/draft-07/schema#";
+ if (definitions[defKey]) {
+ if (!deepEqual(definitions[defKey], nestedDefinitions[defKey])) {
+ if (Object.keys(definitions[defKey]).every((k) => k === "$ref" || k === "$schema")) {
+ definitions[defKey] = nestedDefinitions[defKey];
+ console.log(yellowBright("\nWARNING"), "Overwriting definition for", defKey, "with nested definition (ref/schema only).");
+ isNewLine = true;
+ } else {
+ console.log(redBright("\nERROR"), "Conflicting definition for", defKey, "found in main definitions.");
+ console.log(columnizedObjectDiff(definitions[defKey], nestedDefinitions[defKey], true));
+ console.log("Keys:", Object.keys(definitions[defKey]), Object.keys(nestedDefinitions[defKey]));
+ isNewLine = true;
+ }
+ } else {
+ // console.log("Definition", defKey, "is identical to existing definition, skipping.");
+ }
+ } else {
+ definitions[defKey] = nestedDefinitions[defKey];
+ if (isNewLine) {
+ process.stdout.write("Adding nested definitions to main definitions: ");
+ isNewLine = false;
+ } else process.stdout.write("\x1b[4D, ");
+ process.stdout.write(yellowBright(defKey) + "... ");
+ }
+ }
- deleteOneOfKindUndefinedRecursive(definitions, "$");
- for (const defKey in definitions) {
- filterSchema(definitions[defKey]);
- }
+ deleteOneOfKindUndefinedRecursive(definitions, "$");
+ for (const defKey in definitions) {
+ filterSchema(definitions[defKey]);
+ }
- if (process.env.WRITE_SCHEMA_DIR === "true") {
- await Promise.all(writePromises);
- await Promise.all(
- Object.keys(definitions).map(async (name) => {
- await fsp.writeFile(path.join("schemas_final", `${name}.json`), JSON.stringify(definitions[name], null, 4));
- // console.log("Wrote schema", name, "to schemas/");
- }),
- );
- await Promise.all(
- Object.keys(nestedDefinitions).map(async (name) => {
- await fsp.writeFile(path.join("schemas_nested", `${name}.json`), JSON.stringify(nestedDefinitions[name], null, 4));
- // console.log("Wrote schema", name, "to schemas_nested/");
- }),
- );
- }
+ if (process.env.WRITE_SCHEMA_DIR === "true") {
+ await Promise.all(writePromises);
+ await Promise.all(
+ Object.keys(definitions).map(async (name) => {
+ await fsp.writeFile(path.join("schemas_final", `${name}.json`), JSON.stringify(definitions[name], null, 4));
+ // console.log("Wrote schema", name, "to schemas/");
+ }),
+ );
+ await Promise.all(
+ Object.keys(nestedDefinitions).map(async (name) => {
+ await fsp.writeFile(path.join("schemas_nested", `${name}.json`), JSON.stringify(nestedDefinitions[name], null, 4));
+ // console.log("Wrote schema", name, "to schemas_nested/");
+ }),
+ );
+ }
- fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
- fs.writeFileSync(__dirname + "/schemaExclusions.json", JSON.stringify(exclusionList, null, 4));
- const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds);
- console.log("\nSuccessfully wrote", Object.keys(definitions).length, "schemas to", schemaPath, "in", elapsedMs, "ms,", fs.statSync(schemaPath).size, "bytes.");
+ fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
+ fs.writeFileSync(__dirname + "/schemaExclusions.json", JSON.stringify(exclusionList, null, 4));
+ const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds);
+ console.log("\nSuccessfully wrote", Object.keys(definitions).length, "schemas to", schemaPath, "in", elapsedMs, "ms,", fs.statSync(schemaPath).size, "bytes.");
}
function deleteOneOfKindUndefinedRecursive(obj, path) {
- if (obj?.type === "object" && obj?.properties?.oneofKind?.type === "undefined") return true;
+ if (obj?.type === "object" && obj?.properties?.oneofKind?.type === "undefined") return true;
- for (const key in obj) {
- if (typeof obj[key] === "object" && deleteOneOfKindUndefinedRecursive(obj[key], path + "." + key)) {
- console.log("Deleting", path, key);
- delete obj[key];
- }
- }
+ for (const key in obj) {
+ if (typeof obj[key] === "object" && deleteOneOfKindUndefinedRecursive(obj[key], path + "." + key)) {
+ console.log("Deleting", path, key);
+ delete obj[key];
+ }
+ }
- return false;
+ return false;
}
function filterSchema(schema) {
- // this is a hack. we may want to check if its a @column instead
- if (schema.properties) {
- for (let key in schema.properties) {
- if (baseClassProperties.includes(key)) {
- delete schema.properties[key];
- }
- }
- }
+ // this is a hack. we may want to check if its a @column instead
+ if (schema.properties) {
+ for (let key in schema.properties) {
+ if (baseClassProperties.includes(key)) {
+ delete schema.properties[key];
+ }
+ }
+ }
- if (schema.required) schema.required = schema.required.filter((x) => !baseClassProperties.includes(x));
+ if (schema.required) schema.required = schema.required.filter((x) => !baseClassProperties.includes(x));
- // recurse into own definitions
- if (schema.definitions) {
- console.log(redBright("WARNING"), "Schema has own definitions, recursing into them to filter base class properties:", Object.keys(schema.definitions));
- for (const defKey in schema.definitions) {
- filterSchema(schema.definitions[defKey]);
- }
- }
+ // recurse into own definitions
+ if (schema.definitions) {
+ console.log(redBright("WARNING"), "Schema has own definitions, recursing into them to filter base class properties:", Object.keys(schema.definitions));
+ for (const defKey in schema.definitions) {
+ filterSchema(schema.definitions[defKey]);
+ }
+ }
}
function deepEqual(a, b) {
- if (a === b) return true;
+ if (a === b) return true;
- if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) {
- return false;
- }
+ if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) {
+ return false;
+ }
- const keysA = Object.keys(a);
- const keysB = Object.keys(b);
+ const keysA = Object.keys(a);
+ const keysB = Object.keys(b);
- if (keysA.length !== keysB.length) return false;
+ if (keysA.length !== keysB.length) return false;
- for (const key of keysA) {
- if (!keysB.includes(key) || (typeof a[key] === typeof b[key] && !deepEqual(a[key], b[key]))) {
- return false;
- }
- }
+ for (const key of keysA) {
+ if (!keysB.includes(key) || (typeof a[key] === typeof b[key] && !deepEqual(a[key], b[key]))) {
+ return false;
+ }
+ }
- return true;
+ return true;
}
function columnizedObjectDiff(a, b, trackEqual = false) {
- const diffs = { left: {}, right: {}, ...(trackEqual ? { equal: {} } : {}) };
- const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
- for (const key of keys) {
- if (!deepEqual(a[key], b[key])) {
- diffs.left[key] = a[key];
- diffs.right[key] = b[key];
- } else if (trackEqual) diffs.equal[key] = a[key];
- }
- return diffs;
+ const diffs = { left: {}, right: {}, ...(trackEqual ? { equal: {} } : {}) };
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
+ for (const key of keys) {
+ if (!deepEqual(a[key], b[key])) {
+ diffs.left[key] = a[key];
+ diffs.right[key] = b[key];
+ } else if (trackEqual) diffs.equal[key] = a[key];
+ }
+ return diffs;
}
main();
diff --git a/scripts/schemaExclusions.json b/scripts/schemaExclusions.json
index 389cb4f51..64e2bbf00 100644
--- a/scripts/schemaExclusions.json
+++ b/scripts/schemaExclusions.json
@@ -1,409 +1,415 @@
{
- "include": ["MessageInteractionSchema"],
- "includeRe": ["^MessageComponentType\\..*"],
- "manual": [
- "DefaultSchema",
- "Schema",
- "EntitySchema",
- "ReadableStream",
- "SomeJSONSchema",
- "UncheckedPartialSchema",
- "PartialSchema",
- "UncheckedPropertiesSchema",
- "PropertiesSchema",
- "AsyncSchema",
- "AnySchema",
- "SMTPConnection.CustomAuthenticationResponse",
- "TransportMakeRequestResponse",
- "StaticSchema",
- "CSVImportResponse",
- "NewMultipleMembersResponse",
- "EventsResponse",
- "NotificationAPIResponse",
- "MetricsAPIResponse",
- "ValidationResponse",
- "MultipleValidationJobsListResponse",
- "UpdateRouteResponse",
- "MessagesSendAPIResponse",
- "APIWebhook",
- "WebhookResponse",
- "WebhookValidationResponse",
- "MessageResponse",
- "ConnectionSettingsResponse",
- "UpdatedDKIMSelectorResponse",
- "UpdatedWebPrefixResponse",
- "APIResponse",
- "APIErrorOptions",
- "APIErrorType",
- "ListTagsForResourceResponse",
- "GetContactResponse",
- "LibraryResponse",
- "LibraryLocalResponse",
- "SchemaTraits",
- "TraitsSchema",
- "AbuseIpDbCheckResponse",
- "IpDataIpLookupResponse"
- ],
- "manualRe": [
- ".*\\.Response$",
- "^(Http2Server|Server|Express|(Resolved|)Http|Client|_|)Response$",
- ".*\\..*",
- "^Axios.*",
- "^Internal",
- "^Record<",
- "^Omit<",
- "^ListContact(s|Lists)Response$",
- "^APIKeyConfiguration\\..*",
- "^AccountSetting\\..*",
- "^BulkContactManagement\\..*",
- "^Campaign.*",
- "^Contact.*",
- "^DNS\\..*",
- "^Delete.*",
- "^Destroy.*",
- "^Template\\..*",
- "^Webhook\\..*",
- "^(BigDecimal|BigInteger|Blob|Boolean|Document|Error|LazyRequest|List|Map|Normalized|Numeric|StreamingBlob|TimestampDateTime|TimestampHttpDate|TimestampEpochSeconds|Simple)Schema",
- "^((Create|Update)Contact(|List))Response$",
- "^(T|Unt)agResourceResponse$",
- "^Put",
- "^Inbox",
- "^Seed",
- "^DomainTag",
- "^IpPool",
- "DomainTemplate",
- "^\\$",
- "^Suppression",
- "^Mail(|ing)List",
- "DomainTracking",
- "UpdatedDomain",
- "ConfigurationSet",
- "ContactList",
- "^IPR",
- "^Job"
- ],
- "manualWarn": [],
- "manualWarnRe": [".*<.*>$"],
- "auto": [
- {
- "value": "StringSchema",
- "reason": "AWS Smithy schema"
- },
- {
- "value": "TimestampDefaultSchema",
- "reason": "AWS Smithy schema"
- },
- {
- "value": "StaticSimpleSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "StaticListSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "StaticMapSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "StaticStructureSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "StaticErrorSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "StaticOperationSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "UnitSchema",
- "reason": "AWS Smithy schema"
- },
- {
- "value": "MemberSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "StructureSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "OperationSchema",
- "reason": "Self-reference only schema"
- },
- {
- "value": "BatchGetMetricDataResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CancelExportJobResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateCustomVerificationEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateDedicatedIpPoolResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateDeliverabilityTestReportResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateEmailIdentityResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateEmailIdentityPolicyResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateExportJobResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateImportJobResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateMultiRegionEndpointResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateTenantResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "CreateTenantResourceAssociationResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetAccountResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetBlacklistReportsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetCustomVerificationEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDedicatedIpResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDedicatedIpPoolResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDedicatedIpsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDeliverabilityDashboardOptionsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDeliverabilityTestReportResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDomainDeliverabilityCampaignResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetDomainStatisticsReportResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetEmailIdentityResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetEmailIdentityPoliciesResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetExportJobResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetImportJobResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetMessageInsightsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetMultiRegionEndpointResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetReputationEntityResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetSuppressedDestinationResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "GetTenantResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListCustomVerificationEmailTemplatesResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListDedicatedIpPoolsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListDeliverabilityTestReportsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListDomainDeliverabilityCampaignsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListEmailIdentitiesResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListEmailTemplatesResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListExportJobsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListImportJobsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListMultiRegionEndpointsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListRecommendationsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListReputationEntitiesResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListResourceTenantsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListSuppressedDestinationsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListTenantResourcesResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "ListTenantsResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "SendBulkEmailResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "SendCustomVerificationEmailResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "SendEmailResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "TestRenderEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "UpdateCustomVerificationEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "UpdateEmailIdentityPolicyResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "UpdateEmailTemplateResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "UpdateReputationEntityCustomerManagedStatusResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "UpdateReputationEntityPolicyResponse",
- "reason": "HTML paragraph in description"
- },
- {
- "value": "UpdatedDKIMAuthorityResponse",
- "reason": "Uses import()"
- },
- {
- "value": "ListRecipientResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "TemplateResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "TemplateDetailContentResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "EventCallbackUrlResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "ParseRouteResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "SenderResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "MetaSenderResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "ApiKeyResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "MyProfileResponse",
- "reason": "Schema with only uppercase properties"
- },
- {
- "value": "UserResponse",
- "reason": "Schema with only uppercase properties"
- }
- ]
-}
+ "include": [
+ "MessageInteractionSchema"
+ ],
+ "includeRe": [
+ "^MessageComponentType\\..*"
+ ],
+ "manual": [
+ "DefaultSchema",
+ "Schema",
+ "EntitySchema",
+ "ReadableStream",
+ "SomeJSONSchema",
+ "UncheckedPartialSchema",
+ "PartialSchema",
+ "UncheckedPropertiesSchema",
+ "PropertiesSchema",
+ "AsyncSchema",
+ "AnySchema",
+ "SMTPConnection.CustomAuthenticationResponse",
+ "TransportMakeRequestResponse",
+ "StaticSchema",
+ "CSVImportResponse",
+ "NewMultipleMembersResponse",
+ "EventsResponse",
+ "NotificationAPIResponse",
+ "MetricsAPIResponse",
+ "ValidationResponse",
+ "MultipleValidationJobsListResponse",
+ "UpdateRouteResponse",
+ "MessagesSendAPIResponse",
+ "APIWebhook",
+ "WebhookResponse",
+ "WebhookValidationResponse",
+ "MessageResponse",
+ "ConnectionSettingsResponse",
+ "UpdatedDKIMSelectorResponse",
+ "UpdatedWebPrefixResponse",
+ "APIResponse",
+ "APIErrorOptions",
+ "APIErrorType",
+ "ListTagsForResourceResponse",
+ "GetContactResponse",
+ "LibraryResponse",
+ "LibraryLocalResponse",
+ "SchemaTraits",
+ "TraitsSchema",
+ "AbuseIpDbCheckResponse",
+ "IpDataIpLookupResponse"
+ ],
+ "manualRe": [
+ ".*\\.Response$",
+ "^(Http2Server|Server|Express|(Resolved|)Http|Client|_|)Response$",
+ ".*\\..*",
+ "^Axios.*",
+ "^Internal",
+ "^Record<",
+ "^Omit<",
+ "^ListContact(s|Lists)Response$",
+ "^APIKeyConfiguration\\..*",
+ "^AccountSetting\\..*",
+ "^BulkContactManagement\\..*",
+ "^Campaign.*",
+ "^Contact.*",
+ "^DNS\\..*",
+ "^Delete.*",
+ "^Destroy.*",
+ "^Template\\..*",
+ "^Webhook\\..*",
+ "^(BigDecimal|BigInteger|Blob|Boolean|Document|Error|LazyRequest|List|Map|Normalized|Numeric|StreamingBlob|TimestampDateTime|TimestampHttpDate|TimestampEpochSeconds|Simple)Schema",
+ "^((Create|Update)Contact(|List))Response$",
+ "^(T|Unt)agResourceResponse$",
+ "^Put",
+ "^Inbox",
+ "^Seed",
+ "^DomainTag",
+ "^IpPool",
+ "DomainTemplate",
+ "^\\$",
+ "^Suppression",
+ "^Mail(|ing)List",
+ "DomainTracking",
+ "UpdatedDomain",
+ "ConfigurationSet",
+ "ContactList",
+ "^IPR",
+ "^Job"
+ ],
+ "manualWarn": [],
+ "manualWarnRe": [
+ ".*<.*>$"
+ ],
+ "auto": [
+ {
+ "value": "StringSchema",
+ "reason": "AWS Smithy schema"
+ },
+ {
+ "value": "TimestampDefaultSchema",
+ "reason": "AWS Smithy schema"
+ },
+ {
+ "value": "StaticSimpleSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "StaticListSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "StaticMapSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "StaticStructureSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "StaticErrorSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "StaticOperationSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "UnitSchema",
+ "reason": "AWS Smithy schema"
+ },
+ {
+ "value": "MemberSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "StructureSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "OperationSchema",
+ "reason": "Self-reference only schema"
+ },
+ {
+ "value": "BatchGetMetricDataResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CancelExportJobResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateCustomVerificationEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateDedicatedIpPoolResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateDeliverabilityTestReportResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateEmailIdentityResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateEmailIdentityPolicyResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateExportJobResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateImportJobResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateMultiRegionEndpointResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateTenantResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "CreateTenantResourceAssociationResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetAccountResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetBlacklistReportsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetCustomVerificationEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDedicatedIpResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDedicatedIpPoolResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDedicatedIpsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDeliverabilityDashboardOptionsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDeliverabilityTestReportResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDomainDeliverabilityCampaignResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetDomainStatisticsReportResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetEmailIdentityResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetEmailIdentityPoliciesResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetExportJobResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetImportJobResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetMessageInsightsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetMultiRegionEndpointResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetReputationEntityResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetSuppressedDestinationResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "GetTenantResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListCustomVerificationEmailTemplatesResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListDedicatedIpPoolsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListDeliverabilityTestReportsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListDomainDeliverabilityCampaignsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListEmailIdentitiesResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListEmailTemplatesResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListExportJobsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListImportJobsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListMultiRegionEndpointsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListRecommendationsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListReputationEntitiesResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListResourceTenantsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListSuppressedDestinationsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListTenantResourcesResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "ListTenantsResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "SendBulkEmailResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "SendCustomVerificationEmailResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "SendEmailResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "TestRenderEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "UpdateCustomVerificationEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "UpdateEmailIdentityPolicyResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "UpdateEmailTemplateResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "UpdateReputationEntityCustomerManagedStatusResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "UpdateReputationEntityPolicyResponse",
+ "reason": "HTML paragraph in description"
+ },
+ {
+ "value": "UpdatedDKIMAuthorityResponse",
+ "reason": "Uses import()"
+ },
+ {
+ "value": "ListRecipientResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "TemplateResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "TemplateDetailContentResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "EventCallbackUrlResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "ParseRouteResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "SenderResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "MetaSenderResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "ApiKeyResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "MyProfileResponse",
+ "reason": "Schema with only uppercase properties"
+ },
+ {
+ "value": "UserResponse",
+ "reason": "Schema with only uppercase properties"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/scripts/stress/identify.js b/scripts/stress/identify.js
index 478643f5f..08d8d5db8 100644
--- a/scripts/stress/identify.js
+++ b/scripts/stress/identify.js
@@ -8,45 +8,45 @@ const TOKEN = process.env.TOKEN;
const TOTAL_ITERATIONS = process.env.ITER ? parseInt(process.env.ITER) : 500;
const doTimedIdentify = () =>
- new Promise((resolve) => {
- let start;
- const ws = new WebSocket(ENDPOINT);
- ws.on("message", (data) => {
- const parsed = JSON.parse(data);
+ new Promise((resolve) => {
+ let start;
+ const ws = new WebSocket(ENDPOINT);
+ ws.on("message", (data) => {
+ const parsed = JSON.parse(data);
- switch (parsed.op) {
- case OPCODES.Hello:
- // send identify
- start = performance.now();
- ws.send(
- JSON.stringify({
- op: OPCODES.Identify,
- d: {
- token: TOKEN,
- properties: {},
- },
- }),
- );
- break;
- case OPCODES.Dispatch:
- if (parsed.t == "READY") {
- ws.close();
- return resolve(performance.now() - start);
- }
+ switch (parsed.op) {
+ case OPCODES.Hello:
+ // send identify
+ start = performance.now();
+ ws.send(
+ JSON.stringify({
+ op: OPCODES.Identify,
+ d: {
+ token: TOKEN,
+ properties: {},
+ },
+ }),
+ );
+ break;
+ case OPCODES.Dispatch:
+ if (parsed.t == "READY") {
+ ws.close();
+ return resolve(performance.now() - start);
+ }
- break;
- }
- });
- });
+ break;
+ }
+ });
+ });
(async () => {
- const perfs = [];
- while (perfs.length < TOTAL_ITERATIONS) {
- const ret = await doTimedIdentify();
- perfs.push(ret);
- // console.log(`${perfs.length}/${TOTAL_ITERATIONS} - this: ${Math.floor(ret)}ms`)
- }
+ const perfs = [];
+ while (perfs.length < TOTAL_ITERATIONS) {
+ const ret = await doTimedIdentify();
+ perfs.push(ret);
+ // console.log(`${perfs.length}/${TOTAL_ITERATIONS} - this: ${Math.floor(ret)}ms`)
+ }
- const avg = perfs.reduce((prev, curr) => prev + curr) / (perfs.length - 1);
- console.log(`Average identify time: ${Math.floor(avg * 100) / 100}ms`);
+ const avg = perfs.reduce((prev, curr) => prev + curr) / (perfs.length - 1);
+ console.log(`Average identify time: ${Math.floor(avg * 100) / 100}ms`);
})();
diff --git a/scripts/stress/login.js b/scripts/stress/login.js
index 5cb254d16..830d6a48c 100644
--- a/scripts/stress/login.js
+++ b/scripts/stress/login.js
@@ -1,16 +1,16 @@
const ENDPOINT = process.env.API || "http://localhost:3001";
async function main() {
- const ret = await fetch(`${ENDPOINT}/api/auth/login`, {
- method: "POST",
- body: JSON.stringify({
- login: process.argv[2],
- password: process.argv[3],
- }),
- headers: { "content-type": "application/json " },
- });
+ const ret = await fetch(`${ENDPOINT}/api/auth/login`, {
+ method: "POST",
+ body: JSON.stringify({
+ login: process.argv[2],
+ password: process.argv[3],
+ }),
+ headers: { "content-type": "application/json " },
+ });
- console.log((await ret.json()).token);
+ console.log((await ret.json()).token);
}
main();
diff --git a/scripts/stress/users.js b/scripts/stress/users.js
index 67fa3ec88..2fc41eaef 100644
--- a/scripts/stress/users.js
+++ b/scripts/stress/users.js
@@ -21,22 +21,22 @@ const count = Number(process.env.COUNT) || 50;
const endpoint = process.env.API || "http://localhost:3001";
async function main() {
- for (let i = 0; i < count; i++) {
- fetch(`${endpoint}/api/auth/register`, {
- method: "POST",
- body: JSON.stringify({
- fingerprint: `${i}.wR8vi8lGlFBJerErO9LG5NViJFw`,
- username: `test${i}`,
- invite: null,
- consent: true,
- date_of_birth: "2000-01-01",
- gift_code_sku_id: null,
- captcha_key: null,
- }),
- headers: { "content-type": "application/json" },
- });
- console.log(i);
- }
+ for (let i = 0; i < count; i++) {
+ fetch(`${endpoint}/api/auth/register`, {
+ method: "POST",
+ body: JSON.stringify({
+ fingerprint: `${i}.wR8vi8lGlFBJerErO9LG5NViJFw`,
+ username: `test${i}`,
+ invite: null,
+ consent: true,
+ date_of_birth: "2000-01-01",
+ gift_code_sku_id: null,
+ captcha_key: null,
+ }),
+ headers: { "content-type": "application/json" },
+ });
+ console.log(i);
+ }
}
main();
diff --git a/scripts/syncronise.js b/scripts/syncronise.js
index 1f3ed112a..58c18029f 100644
--- a/scripts/syncronise.js
+++ b/scripts/syncronise.js
@@ -30,9 +30,9 @@ require("dotenv").config({ quiet: true });
const { initDatabase } = require("..");
(async () => {
- const db = await initDatabase();
- console.log("synchronising");
- await db.synchronize();
- console.log("done");
- db.destroy();
+ const db = await initDatabase();
+ console.log("synchronising");
+ await db.synchronize();
+ console.log("done");
+ db.destroy();
})();
diff --git a/scripts/test.js b/scripts/test.js
index c79e336ff..3f944138f 100644
--- a/scripts/test.js
+++ b/scripts/test.js
@@ -29,34 +29,34 @@ const cfgFile = path.join(__dirname, "test_config.json");
process.env.CONFIG_PATH = cfgFile;
fs.writeFileSync(
- cfgFile,
- JSON.stringify({
- api: { endpointPublic: "http://localhost:3001/api/v9/" },
- cdn: { endpointPublic: "http://localhost:3001/", endpointPrivate: "http://localhost:3001/" },
- gateway: { endpointPublic: "ws://localhost:3001/" },
- }),
+ cfgFile,
+ JSON.stringify({
+ api: { endpointPublic: "http://localhost:3001/api/v9/" },
+ cdn: { endpointPublic: "http://localhost:3001/", endpointPrivate: "http://localhost:3001/" },
+ gateway: { endpointPublic: "ws://localhost:3001/" },
+ }),
);
const server = spawn("node", [path.join(__dirname, "..", "dist", "bundle", "start.js")]);
server.stdout.on("data", (data) => {
- process.stdout.write(data);
+ process.stdout.write(data);
- if (data.toString().toLowerCase().includes("listening")) {
- // we good :)
- console.log("we good");
- server.kill();
- process.exit();
- }
+ if (data.toString().toLowerCase().includes("listening")) {
+ // we good :)
+ console.log("we good");
+ server.kill();
+ process.exit();
+ }
});
server.stderr.on("data", (err) => {
- process.stdout.write(err);
- // we bad :(
- process.kill(1);
+ process.stdout.write(err);
+ // we bad :(
+ process.kill(1);
});
server.on("close", (code) => {
- console.log("closed with code", code);
- process.exit(code);
+ console.log("closed with code", code);
+ process.exit(code);
});
diff --git a/scripts/util/getRouteDescriptions.js b/scripts/util/getRouteDescriptions.js
index 1f27141ee..87f1d4eb0 100644
--- a/scripts/util/getRouteDescriptions.js
+++ b/scripts/util/getRouteDescriptions.js
@@ -15,24 +15,24 @@ let currentPath = "";
*/
function colorizeMethod(method) {
- switch (method.toLowerCase()) {
- case "get":
- return greenBright(method.toUpperCase());
- case "post":
- return yellowBright(method.toUpperCase());
- case "put":
- return blueBright(method.toUpperCase());
- case "delete":
- return redBright(method.toUpperCase());
- case "patch":
- return yellowBright(method.toUpperCase());
- default:
- return method.toUpperCase();
- }
+ switch (method.toLowerCase()) {
+ case "get":
+ return greenBright(method.toUpperCase());
+ case "post":
+ return yellowBright(method.toUpperCase());
+ case "put":
+ return blueBright(method.toUpperCase());
+ case "delete":
+ return redBright(method.toUpperCase());
+ case "patch":
+ return yellowBright(method.toUpperCase());
+ default:
+ return method.toUpperCase();
+ }
}
function formatPath(path) {
- return path.replace(/:(\w+)/g, underline(":$1")).replace(/#(\w+)/g, underline("#$1"));
+ return path.replace(/:(\w+)/g, underline(":$1")).replace(/#(\w+)/g, underline("#$1"));
}
/**
@@ -43,45 +43,45 @@ function formatPath(path) {
* @param args
*/
function proxy(file, apiMethod, apiPathPrefix, apiPath, ...args) {
- const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
- if (!opts)
- return console.error(
- ` \x1b[5m${bgRedBright("ERROR")}\x1b[25m ${file.replace(path.resolve(__dirname, "..", "..", "dist"), "/src")} has route without route() description middleware: ${colorizeMethod(apiMethod)} ${formatPath(apiPath)}`,
- );
+ const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
+ if (!opts)
+ return console.error(
+ ` \x1b[5m${bgRedBright("ERROR")}\x1b[25m ${file.replace(path.resolve(__dirname, "..", "..", "dist"), "/src")} has route without route() description middleware: ${colorizeMethod(apiMethod)} ${formatPath(apiPath)}`,
+ );
- console.log(`${colorizeMethod(apiMethod).padStart("DELETE".length + 10)} ${formatPath(apiPathPrefix + apiPath)}`);
- opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
- routes.set(apiPathPrefix + apiPath + "|" + apiMethod, opts());
+ console.log(`${colorizeMethod(apiMethod).padStart("DELETE".length + 10)} ${formatPath(apiPathPrefix + apiPath)}`);
+ opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
+ routes.set(apiPathPrefix + apiPath + "|" + apiMethod, opts());
}
express.Router = () => {
- return Object.fromEntries(methods.map((method) => [method, proxy.bind(null, currentFile, method, currentPath)]));
+ return Object.fromEntries(methods.map((method) => [method, proxy.bind(null, currentFile, method, currentPath)]));
};
RouteUtility.route = (opts) => {
- const func = function () {
- return opts;
- };
- func.prototype.OPTS_MARKER = true;
- return func;
+ const func = function () {
+ return opts;
+ };
+ func.prototype.OPTS_MARKER = true;
+ return func;
};
module.exports = function getRouteDescriptions() {
- const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/");
- traverseDirectory({ dirname: root, recursive: true }, (file) => {
- currentFile = file;
+ const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/");
+ traverseDirectory({ dirname: root, recursive: true }, (file) => {
+ currentFile = file;
- currentPath = file.replace(root.slice(0, -1), "");
- currentPath = currentPath.split(".").slice(0, -1).join("."); // truncate .js/.ts file extension of path
- currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
- if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
+ currentPath = file.replace(root.slice(0, -1), "");
+ currentPath = currentPath.split(".").slice(0, -1).join("."); // truncate .js/.ts file extension of path
+ currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
+ if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
- try {
- require(file);
- } catch (e) {
- console.error(e);
- }
- });
+ try {
+ require(file);
+ } catch (e) {
+ console.error(e);
+ }
+ });
- return routes;
+ return routes;
};
diff --git a/scripts/util/walk.js b/scripts/util/walk.js
index be59023d9..065433155 100644
--- a/scripts/util/walk.js
+++ b/scripts/util/walk.js
@@ -20,18 +20,18 @@ const fs = require("fs");
/** dir: string. types: string[] ( file types ) */
module.exports = function walk(dir, types = ["ts"]) {
- 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, types));
- } else {
- if (!types.find((x) => file.endsWith(x))) return;
- results.push(file);
- }
- });
- return results;
+ 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, types));
+ } else {
+ if (!types.find((x) => file.endsWith(x))) return;
+ results.push(file);
+ }
+ });
+ return results;
};
diff --git a/server.code-workspace b/server.code-workspace
index c3ffcffa7..d73553d92 100644
--- a/server.code-workspace
+++ b/server.code-workspace
@@ -1,23 +1,23 @@
{
- "folders": [
- {
- "path": "src",
- },
- {
- "path": "assets",
- },
- {
- "path": "scripts",
- },
- {
- "path": ".",
- },
- ],
- "settings": {
- "typescript.tsdk": "util\\node_modules\\typescript\\lib",
- },
- "launch": {
- "version": "0.2.0",
- "configurations": [],
- },
+ "folders": [
+ {
+ "path": "src",
+ },
+ {
+ "path": "assets",
+ },
+ {
+ "path": "scripts",
+ },
+ {
+ "path": ".",
+ },
+ ],
+ "settings": {
+ "typescript.tsdk": "util\\node_modules\\typescript\\lib",
+ },
+ "launch": {
+ "version": "0.2.0",
+ "configurations": [],
+ },
}
diff --git a/src/api/Server.ts b/src/api/Server.ts
index 0969e5e82..f81898e81 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -31,149 +31,149 @@ const PUBLIC_ASSETS_FOLDER = path.join(ASSETS_FOLDER, "public");
export type SpacebarServerOptions = ServerOptions;
declare global {
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace Express {
- interface Request {
- server: SpacebarServer;
- }
- }
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ server: SpacebarServer;
+ }
+ }
}
export class SpacebarServer extends Server {
- declare public options: SpacebarServerOptions;
+ declare public options: SpacebarServerOptions;
- constructor(opts?: Partial) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- super({ ...opts, errorHandler: false, jsonBody: false });
- }
+ constructor(opts?: Partial) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ super({ ...opts, errorHandler: false, jsonBody: false });
+ }
- async start() {
- await initDatabase();
- await Config.init();
- await initEvent();
- await Email.init();
- await ConnectionConfig.init();
- await initInstance();
- WebAuthn.init();
+ async start() {
+ await initDatabase();
+ await Config.init();
+ await initEvent();
+ await Email.init();
+ await ConnectionConfig.init();
+ await initInstance();
+ WebAuthn.init();
- const logRequests = process.env["LOG_REQUESTS"] != undefined;
- if (logRequests) {
- this.app.use(
- morgan("combined", {
- skip: (req, res) => {
- let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
- if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
- return skip;
- },
- }),
- );
- }
+ const logRequests = process.env["LOG_REQUESTS"] != undefined;
+ if (logRequests) {
+ this.app.use(
+ morgan("combined", {
+ skip: (req, res) => {
+ let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
+ if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
+ return skip;
+ },
+ }),
+ );
+ }
- this.app.set("json replacer", JSONReplacer);
- this.app.disable("x-powered-by");
+ this.app.set("json replacer", JSONReplacer);
+ this.app.disable("x-powered-by");
- const trustedProxies = Config.get().security.trustedProxies;
- if (trustedProxies) this.app.set("trust proxy", trustedProxies);
+ const trustedProxies = Config.get().security.trustedProxies;
+ if (trustedProxies) this.app.set("trust proxy", trustedProxies);
- this.app.use(CORS);
- this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
+ this.app.use(CORS);
+ this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
- const app = this.app;
- const api = Router({ mergeParams: true });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- this.app = api;
+ const app = this.app;
+ const api = Router({ mergeParams: true });
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ this.app = api;
- api.use(Authentication);
- await initRateLimits(api);
- await initTranslation(api);
+ api.use(Authentication);
+ await initRateLimits(api);
+ await initTranslation(api);
- this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r);
+ this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r);
- // 404 is not an error in express, so this should not be an error middleware
- // this is a fine place to put the 404 handler because its after we register the routes
- // and since its not an error middleware, our error handler below still works.
- // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now
- api.use("*_", (req: Request, res: Response) => {
- res.status(404).json({
- message: "404 endpoint not found",
- code: 0,
- });
- });
+ // 404 is not an error in express, so this should not be an error middleware
+ // this is a fine place to put the 404 handler because its after we register the routes
+ // and since its not an error middleware, our error handler below still works.
+ // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now
+ api.use("*_", (req: Request, res: Response) => {
+ res.status(404).json({
+ message: "404 endpoint not found",
+ code: 0,
+ });
+ });
- this.app = app;
+ this.app = app;
- //app.use("/__development", )
- //app.use("/__internals", )
- app.use("/api/v6", api);
- app.use("/api/v7", api);
- app.use("/api/v8", api);
- app.use("/api/v9", api);
- app.use("/api", api); // allow unversioned requests
+ //app.use("/__development", )
+ //app.use("/__internals", )
+ app.use("/api/v6", api);
+ app.use("/api/v7", api);
+ app.use("/api/v8", api);
+ app.use("/api/v9", api);
+ app.use("/api", api); // allow unversioned requests
- app.use("/imageproxy/:hash/:size/:url", ImageProxy);
+ app.use("/imageproxy/:hash/:size/:url", ImageProxy);
- app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
+ app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
- app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html")));
+ app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html")));
- app.get("/_spacebar/api/schemas.json", (req, res) => {
- res.sendFile(path.join(ASSETS_FOLDER, "schemas.json"));
- });
+ app.get("/_spacebar/api/schemas.json", (req, res) => {
+ res.sendFile(path.join(ASSETS_FOLDER, "schemas.json"));
+ });
- app.get("/_spacebar/api/openapi.json", (req, res) => {
- res.sendFile(path.join(ASSETS_FOLDER, "openapi.json"));
- });
+ app.get("/_spacebar/api/openapi.json", (req, res) => {
+ res.sendFile(path.join(ASSETS_FOLDER, "openapi.json"));
+ });
- // current well-known location
- app.get("/.well-known/spacebar", (req, res) => {
- res.json({
- api: Config.get().api.endpointPublic,
- });
- });
+ // current well-known location
+ app.get("/.well-known/spacebar", (req, res) => {
+ res.json({
+ api: Config.get().api.endpointPublic,
+ });
+ });
- // new well-known location
- app.get("/.well-known/spacebar/client", (req, res) => {
- let erlpackSupported = false;
- try {
- require("@yukikaze-bot/erlpack");
- erlpackSupported = true;
- } catch (e) {
- // empty
- }
+ // new well-known location
+ app.get("/.well-known/spacebar/client", (req, res) => {
+ let erlpackSupported = false;
+ try {
+ require("@yukikaze-bot/erlpack");
+ erlpackSupported = true;
+ } catch (e) {
+ // empty
+ }
- res.json({
- api: {
- baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9
- apiVersions: {
- default: Config.get().api.defaultVersion,
- active: Config.get().api.activeVersions,
- },
- },
- cdn: {
- baseUrl: Config.get().cdn.endpointPublic,
- },
- gateway: {
- baseUrl: Config.get().gateway.endpointPublic,
- encoding: [...(erlpackSupported ? ["etf"] : []), "json"],
- compression: ["zstd-stream", "zlib-stream", null],
- },
- admin:
- Config.get().admin.endpointPublic === null
- ? undefined
- : {
- baseUrl: Config.get().admin.endpointPublic,
- },
- });
- });
+ res.json({
+ api: {
+ baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9
+ apiVersions: {
+ default: Config.get().api.defaultVersion,
+ active: Config.get().api.activeVersions,
+ },
+ },
+ cdn: {
+ baseUrl: Config.get().cdn.endpointPublic,
+ },
+ gateway: {
+ baseUrl: Config.get().gateway.endpointPublic,
+ encoding: [...(erlpackSupported ? ["etf"] : []), "json"],
+ compression: ["zstd-stream", "zlib-stream", null],
+ },
+ admin:
+ Config.get().admin.endpointPublic === null
+ ? undefined
+ : {
+ baseUrl: Config.get().admin.endpointPublic,
+ },
+ });
+ });
- this.app.use(ErrorHandler);
+ this.app.use(ErrorHandler);
- ConnectionLoader.loadConnections();
+ ConnectionLoader.loadConnections();
- if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`));
+ if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`));
- return super.start();
- }
+ return super.start();
+ }
}
diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 47f6ab377..078a566b2 100644
--- a/src/api/middlewares/Authentication.ts
+++ b/src/api/middlewares/Authentication.ts
@@ -21,112 +21,112 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server";
export const NO_AUTHORIZATION_ROUTES = [
- // Authentication routes
- "POST /auth/login",
- "POST /auth/register",
- "GET /auth/location-metadata",
- "POST /auth/mfa/",
- "POST /auth/verify",
- "POST /auth/forgot",
- "POST /auth/reset",
- "POST /auth/fingerprint",
- "GET /invites/",
- // Routes with a seperate auth system
- /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
- /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/,
- // Public information endpoints
- "GET /ping",
- "GET /gateway",
- "GET /experiments",
- "GET /updates",
- "GET /download",
- "GET /scheduled-maintenances/upcoming.json",
- // Public kubernetes integration
- "GET /-/readyz",
- "GET /-/healthz",
- // Client analytics
- "POST /science",
- "POST /track",
- // Public policy pages
- "GET /policies/instance/",
- // Oauth callback
- "/oauth2/callback",
- // Asset delivery
- /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/,
- // Connections
- /^(POST|HEAD) \/connections\/\w+\/callback/,
- // Image proxy
- /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/,
+ // Authentication routes
+ "POST /auth/login",
+ "POST /auth/register",
+ "GET /auth/location-metadata",
+ "POST /auth/mfa/",
+ "POST /auth/verify",
+ "POST /auth/forgot",
+ "POST /auth/reset",
+ "POST /auth/fingerprint",
+ "GET /invites/",
+ // Routes with a seperate auth system
+ /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
+ /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/,
+ // Public information endpoints
+ "GET /ping",
+ "GET /gateway",
+ "GET /experiments",
+ "GET /updates",
+ "GET /download",
+ "GET /scheduled-maintenances/upcoming.json",
+ // Public kubernetes integration
+ "GET /-/readyz",
+ "GET /-/healthz",
+ // Client analytics
+ "POST /science",
+ "POST /track",
+ // Public policy pages
+ "GET /policies/instance/",
+ // Oauth callback
+ "/oauth2/callback",
+ // Asset delivery
+ /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/,
+ // Connections
+ /^(POST|HEAD) \/connections\/\w+\/callback/,
+ // Image proxy
+ /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/,
];
export const API_PREFIX = /^\/api(\/v\d+)?/;
export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//;
declare global {
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace Express {
- interface Request {
- user_id: string;
- user_bot: boolean;
- token: { id: string; iat: number; ver?: number; did?: string };
- rights: Rights;
- fingerprint?: string;
- }
- }
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ user_id: string;
+ user_bot: boolean;
+ token: { id: string; iat: number; ver?: number; did?: string };
+ rights: Rights;
+ fingerprint?: string;
+ }
+ }
}
export async function Authentication(req: Request, res: Response, next: NextFunction) {
- if (req.method === "OPTIONS") return res.sendStatus(204);
- const url = req.url.replace(API_PREFIX, "");
+ if (req.method === "OPTIONS") return res.sendStatus(204);
+ const url = req.url.replace(API_PREFIX, "");
- if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid=")))
- req.fingerprint = req.headers.cookie
- .split("; ")
- .find((x) => x.startsWith("__sb_sessid="))!
- .split("=")[1];
- // for some reason we need to require here, else the openapi generator fails with "route is not a function"
- else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`);
+ if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid=")))
+ req.fingerprint = req.headers.cookie
+ .split("; ")
+ .find((x) => x.startsWith("__sb_sessid="))!
+ .split("=")[1];
+ // for some reason we need to require here, else the openapi generator fails with "route is not a function"
+ else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`);
- if (
- NO_AUTHORIZATION_ROUTES.some((x) => {
- if (typeof x !== "string") {
- return x.test(req.method + " " + url);
- }
+ if (
+ NO_AUTHORIZATION_ROUTES.some((x) => {
+ if (typeof x !== "string") {
+ return x.test(req.method + " " + url);
+ }
- const fullRoute = req.method + " " + url;
+ const fullRoute = req.method + " " + url;
- if (req.method === "HEAD") {
- const urlPart = x.split(" ").slice(1).join(" ");
- if (urlPart.endsWith("/")) {
- return url.startsWith(urlPart);
- } else {
- return url === urlPart;
- }
- }
+ if (req.method === "HEAD") {
+ const urlPart = x.split(" ").slice(1).join(" ");
+ if (urlPart.endsWith("/")) {
+ return url.startsWith(urlPart);
+ } else {
+ return url === urlPart;
+ }
+ }
- if (x.endsWith("/")) {
- return fullRoute.startsWith(x);
- } else {
- return fullRoute === x;
- }
- })
- )
- return next();
+ if (x.endsWith("/")) {
+ return fullRoute.startsWith(x);
+ } else {
+ return fullRoute === x;
+ }
+ })
+ )
+ return next();
- if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
+ if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
- try {
- const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, {
- ipAddress: req.ip,
- fingerprint: req.fingerprint,
- });
+ try {
+ const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, {
+ ipAddress: req.ip,
+ fingerprint: req.fingerprint,
+ });
- req.token = decoded;
- req.user_id = decoded.id;
- req.user_bot = user.bot;
- req.rights = new Rights(Number(user.rights));
- return next();
- } catch (error) {
- return next(new HTTPError(error!.toString(), 400));
- }
+ req.token = decoded;
+ req.user_id = decoded.id;
+ req.user_bot = user.bot;
+ req.rights = new Rights(Number(user.rights));
+ return next();
+ } catch (error) {
+ return next(new HTTPError(error!.toString(), 400));
+ }
}
diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index e08aad493..a579abb31 100644
--- a/src/api/middlewares/BodyParser.ts
+++ b/src/api/middlewares/BodyParser.ts
@@ -21,31 +21,31 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server";
const errorMessages: { [key: string]: [string, number] } = {
- "entity.too.large": ["Request body too large", 413],
- "entity.parse.failed": ["Invalid JSON body", 400],
- "entity.verify.failed": ["Entity verification failed", 403],
- "request.aborted": ["Request aborted", 400],
- "request.size.invalid": ["Request size did not match content length", 400],
- "stream.encoding.set": ["Stream encoding should not be set", 500],
- "stream.not.readable": ["Stream is not readable", 500],
- "parameters.too.many": ["Too many parameters", 413],
- "charset.unsupported": ["Unsupported charset", 415],
- "encoding.unsupported": ["Unsupported content encoding", 415],
+ "entity.too.large": ["Request body too large", 413],
+ "entity.parse.failed": ["Invalid JSON body", 400],
+ "entity.verify.failed": ["Entity verification failed", 403],
+ "request.aborted": ["Request aborted", 400],
+ "request.size.invalid": ["Request size did not match content length", 400],
+ "stream.encoding.set": ["Stream encoding should not be set", 500],
+ "stream.not.readable": ["Stream is not readable", 500],
+ "parameters.too.many": ["Too many parameters", 413],
+ "charset.unsupported": ["Unsupported charset", 415],
+ "encoding.unsupported": ["Unsupported content encoding", 415],
};
export function BodyParser(opts?: OptionsJson) {
- const jsonParser = bodyParser.json(opts);
+ const jsonParser = bodyParser.json(opts);
- return (req: Request, res: Response, next: NextFunction) => {
- if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
+ return (req: Request, res: Response, next: NextFunction) => {
+ if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
- jsonParser(req, res, (err) => {
- if (err) {
- const [message, status] = errorMessages[err.type] || ["Invalid Body", 400];
- const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message;
- return next(new HTTPError(errorMessage, status));
- }
- next();
- });
- };
+ jsonParser(req, res, (err) => {
+ if (err) {
+ const [message, status] = errorMessages[err.type] || ["Invalid Body", 400];
+ const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message;
+ return next(new HTTPError(errorMessage, status));
+ }
+ next();
+ });
+ };
}
diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 34532d3f5..231862504 100644
--- a/src/api/middlewares/CORS.ts
+++ b/src/api/middlewares/CORS.ts
@@ -21,20 +21,20 @@ import { NextFunction, Request, Response } from "express";
// TODO: config settings
export function CORS(req: Request, res: Response, next: NextFunction) {
- res.set("Access-Control-Allow-Credentials", "true");
- res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
- res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*");
- res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*");
- res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically
- // TODO: use better CSP
- res.set(
- "Content-security-policy",
- "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
- );
+ res.set("Access-Control-Allow-Credentials", "true");
+ res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
+ res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*");
+ res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*");
+ res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically
+ // TODO: use better CSP
+ res.set(
+ "Content-security-policy",
+ "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
+ );
- if (req.method === "OPTIONS") {
- res.status(204).end();
- return;
- }
- next();
+ if (req.method === "OPTIONS") {
+ res.status(204).end();
+ return;
+ }
+ next();
}
diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index cd306c9c3..1a9a18a8d 100644
--- a/src/api/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -22,48 +22,48 @@ import { ApiError, FieldError } from "@spacebar/util";
const EntityNotFoundErrorRegex = /"(\w+)"/;
export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) {
- if (!error) return next();
+ if (!error) return next();
- try {
- let code = 400;
- let httpcode = code;
- let message = error?.toString();
- let errors = undefined;
- let _ajvErrors = undefined;
+ try {
+ let code = 400;
+ let httpcode = code;
+ let message = error?.toString();
+ let errors = undefined;
+ let _ajvErrors = undefined;
- if (error instanceof HTTPError && error.code) code = httpcode = error.code;
- else if (error instanceof ApiError) {
- 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`;
- code = httpcode = 404;
- } else if (error instanceof FieldError) {
- code = Number(error.code);
- message = error.message;
- errors = error.errors;
- _ajvErrors = error._ajvErrors;
- } else if (error?.type == "entity.parse.failed") {
- // body-parser failed
- httpcode = 400;
- code = 50109;
- message = "The request body contains invalid JSON.";
- } else {
- console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
+ if (error instanceof HTTPError && error.code) code = httpcode = error.code;
+ else if (error instanceof ApiError) {
+ 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`;
+ code = httpcode = 404;
+ } else if (error instanceof FieldError) {
+ code = Number(error.code);
+ message = error.message;
+ errors = error.errors;
+ _ajvErrors = error._ajvErrors;
+ } else if (error?.type == "entity.parse.failed") {
+ // body-parser failed
+ httpcode = 400;
+ code = 50109;
+ message = "The request body contains invalid JSON.";
+ } else {
+ console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
- if (req.server?.options?.production) {
- // don't expose internal errors to the user, instead human errors should be thrown as HTTPError
- message = "Internal Server Error";
- }
- code = httpcode = 500;
- }
+ if (req.server?.options?.production) {
+ // don't expose internal errors to the user, instead human errors should be thrown as HTTPError
+ message = "Internal Server Error";
+ }
+ code = httpcode = 500;
+ }
- if (httpcode > 511) httpcode = 400;
+ if (httpcode > 511) httpcode = 400;
- res.status(httpcode).json({ code: code, message, errors, _ajvErrors });
- } catch (error) {
- console.error(`[Internal Server Error] 500`, error);
- return res.status(500).json({ code: 500, message: "Internal Server Error" });
- }
+ res.status(httpcode).json({ code: code, message, errors, _ajvErrors });
+ } catch (error) {
+ console.error(`[Internal Server Error] 500`, error);
+ return res.status(500).json({ code: 500, message: "Internal Server Error" });
+ }
}
diff --git a/src/api/middlewares/ImageProxy.ts b/src/api/middlewares/ImageProxy.ts
index 786901299..e950ea85c 100644
--- a/src/api/middlewares/ImageProxy.ts
+++ b/src/api/middlewares/ImageProxy.ts
@@ -25,9 +25,9 @@ let sharp: undefined | false | { default: typeof import("sharp") } = undefined;
let Jimp: JimpType | undefined = undefined;
try {
- Jimp = require("jimp") as JimpType;
+ Jimp = require("jimp") as JimpType;
} catch {
- // empty
+ // empty
}
let sentImageProxyWarning = false;
@@ -37,101 +37,101 @@ const jimpSupported = new Set(["image/jpeg", "image/png", "image/bmp", "image/ti
const resizeSupported = new Set([...sharpSupported, ...jimpSupported]);
export async function ImageProxy(req: Request, res: Response) {
- const path = req.originalUrl.split("/").slice(2);
+ const path = req.originalUrl.split("/").slice(2);
- // src/api/util/utility/EmbedHandlers.ts getProxyUrl
- const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
+ // src/api/util/utility/EmbedHandlers.ts getProxyUrl
+ const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
- try {
- if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature");
- } catch {
- console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]);
- res.status(403).send("Invalid signature");
- return;
- }
+ try {
+ if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature");
+ } catch {
+ console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]);
+ res.status(403).send("Invalid signature");
+ return;
+ }
- const abort = new AbortController();
- setTimeout(() => abort.abort(), 5000);
+ const abort = new AbortController();
+ setTimeout(() => abort.abort(), 5000);
- const request = await fetch("https://" + path.slice(2).join("/"), {
- headers: {
- "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)",
- },
- signal: abort.signal,
- }).catch((e) => {
- if (e.name === "AbortError") res.status(504).send("Request timed out");
- else res.status(500).send("Unable to proxy origin: " + e.message);
- });
- if (!request) return;
+ const request = await fetch("https://" + path.slice(2).join("/"), {
+ headers: {
+ "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)",
+ },
+ signal: abort.signal,
+ }).catch((e) => {
+ if (e.name === "AbortError") res.status(504).send("Request timed out");
+ else res.status(500).send("Unable to proxy origin: " + e.message);
+ });
+ if (!request) return;
- if (request.status !== 200) {
- res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText);
- return;
- }
+ if (request.status !== 200) {
+ res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText);
+ return;
+ }
- if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) {
- res.status(500).send("Origin did not provide a Content-Type or Content-Length header");
- return;
- }
+ if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) {
+ res.status(500).send("Origin did not provide a Content-Type or Content-Length header");
+ return;
+ }
- // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
- if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) {
- res.status(500).send("Origin provided a Content-Length header that is too large");
- return;
- }
+ // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
+ if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) {
+ res.status(500).send("Origin provided a Content-Length header that is too large");
+ return;
+ }
- // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
- let contentType: string = request.headers.get("Content-Type");
+ // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
+ let contentType: string = request.headers.get("Content-Type");
- const arrayBuffer = await request.arrayBuffer();
- let resultBuffer = Buffer.from(arrayBuffer);
+ const arrayBuffer = await request.arrayBuffer();
+ let resultBuffer = Buffer.from(arrayBuffer);
- if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) {
- if (sharp !== false) {
- try {
- sharp = await import("sharp");
- } catch {
- sharp = false;
- }
- }
+ if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) {
+ if (sharp !== false) {
+ try {
+ sharp = await import("sharp");
+ } catch {
+ sharp = false;
+ }
+ }
- if (sharp === false && !Jimp) {
- try {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore Typings don't fit
- Jimp = await import("jimp");
- } catch {
- sentImageProxyWarning = true;
- console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`);
- }
- }
+ if (sharp === false && !Jimp) {
+ try {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore Typings don't fit
+ Jimp = await import("jimp");
+ } catch {
+ sentImageProxyWarning = true;
+ console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`);
+ }
+ }
- const [width, height] = path[1].split("x").map((x) => parseInt(x));
+ const [width, height] = path[1].split("x").map((x) => parseInt(x));
- const buffer = Buffer.from(arrayBuffer);
- if (sharp && sharpSupported.has(contentType)) {
- resultBuffer = Buffer.from(
- await sharp
- .default(buffer)
- // Sharp doesn't support "scaleToFit"
- .resize(width)
- .toBuffer(),
- );
- } else if (Jimp && jimpSupported.has(contentType)) {
- resultBuffer = await Jimp.read(buffer).then((image) => {
- contentType = image.getMIME();
- return (
- image
- .scaleToFit(width, height)
- // @ts-expect-error Jimp is defined at this point
- .getBufferAsync(Jimp.AUTO)
- );
- });
- }
- }
+ const buffer = Buffer.from(arrayBuffer);
+ if (sharp && sharpSupported.has(contentType)) {
+ resultBuffer = Buffer.from(
+ await sharp
+ .default(buffer)
+ // Sharp doesn't support "scaleToFit"
+ .resize(width)
+ .toBuffer(),
+ );
+ } else if (Jimp && jimpSupported.has(contentType)) {
+ resultBuffer = await Jimp.read(buffer).then((image) => {
+ contentType = image.getMIME();
+ return (
+ image
+ .scaleToFit(width, height)
+ // @ts-expect-error Jimp is defined at this point
+ .getBufferAsync(Jimp.AUTO)
+ );
+ });
+ }
+ }
- res.header("Content-Type", contentType);
- res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds);
+ res.header("Content-Type", contentType);
+ res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds);
- res.send(resultBuffer);
+ res.send(resultBuffer);
}
diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 1a41ad4f6..c749df6ad 100644
--- a/src/api/middlewares/RateLimit.ts
+++ b/src/api/middlewares/RateLimit.ts
@@ -34,188 +34,188 @@ TODO: different for methods (GET/POST)
*/
type RateLimit = {
- id: "global" | "error" | string;
- executor_id: string;
- hits: number;
- blocked: boolean;
- expires_at: Date;
+ id: "global" | "error" | string;
+ executor_id: string;
+ hits: number;
+ blocked: boolean;
+ expires_at: Date;
};
const Cache = new Map();
const EventRateLimit = "RATELIMIT";
export default function rateLimit(opts: {
- bucket?: string;
- window: number;
- count: number;
- bot?: number;
- webhook?: number;
- oauth?: number;
- GET?: number;
- MODIFY?: number;
- error?: boolean;
- success?: boolean;
- onlyIp?: boolean;
+ bucket?: string;
+ window: number;
+ count: number;
+ bot?: number;
+ webhook?: number;
+ oauth?: number;
+ GET?: number;
+ MODIFY?: number;
+ error?: boolean;
+ success?: boolean;
+ onlyIp?: boolean;
}) {
- return async (req: Request, res: Response, next: NextFunction) => {
- // exempt user? if so, immediately short circuit
- if (req.user_id) {
- const rights = await getRights(req.user_id);
- if (rights.has("BYPASS_RATE_LIMITS")) return next();
- }
+ return async (req: Request, res: Response, next: NextFunction) => {
+ // exempt user? if so, immediately short circuit
+ if (req.user_id) {
+ const rights = await getRights(req.user_id);
+ if (rights.has("BYPASS_RATE_LIMITS")) return next();
+ }
- const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
- let executor_id = req.ip || "127.0.0.1";
- if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
+ const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
+ let executor_id = req.ip || "127.0.0.1";
+ if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
- let max_hits = opts.count;
- if (opts.bot && req.user_bot) max_hits = opts.bot;
- if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
- else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
+ let max_hits = opts.count;
+ if (opts.bot && req.user_bot) max_hits = opts.bot;
+ if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
+ else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
- const offender = Cache.get(executor_id + bucket_id);
+ const offender = Cache.get(executor_id + bucket_id);
- res.set("X-RateLimit-Limit", `${max_hits}`)
- .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`)
- .set("X-RateLimit-Bucket", `${bucket_id}`)
- // assuming we aren't blocked, a new window will start after this request
- .set("X-RateLimit-Reset", `${Date.now() + opts.window}`)
- .set("X-RateLimit-Reset-After", `${opts.window}`);
+ res.set("X-RateLimit-Limit", `${max_hits}`)
+ .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`)
+ .set("X-RateLimit-Bucket", `${bucket_id}`)
+ // assuming we aren't blocked, a new window will start after this request
+ .set("X-RateLimit-Reset", `${Date.now() + opts.window}`)
+ .set("X-RateLimit-Reset-After", `${opts.window}`);
- if (offender) {
- let reset = offender.expires_at.getTime();
- let resetAfterMs = reset - Date.now();
- let resetAfterSec = Math.ceil(resetAfterMs / 1000);
+ if (offender) {
+ let reset = offender.expires_at.getTime();
+ let resetAfterMs = reset - Date.now();
+ let resetAfterSec = Math.ceil(resetAfterMs / 1000);
- if (resetAfterMs <= 0) {
- offender.hits = 0;
- offender.expires_at = new Date(Date.now() + opts.window * 1000);
- offender.blocked = false;
+ if (resetAfterMs <= 0) {
+ offender.hits = 0;
+ offender.expires_at = new Date(Date.now() + opts.window * 1000);
+ offender.blocked = false;
- Cache.delete(executor_id + bucket_id);
- }
+ Cache.delete(executor_id + bucket_id);
+ }
- res.set("X-RateLimit-Reset", `${reset}`);
- res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`);
+ res.set("X-RateLimit-Reset", `${reset}`);
+ res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`);
- if (offender.blocked) {
- const global = bucket_id === "global";
- // each block violation pushes the expiry one full window further
- reset += opts.window * 1000;
- offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
- resetAfterMs = reset - Date.now();
- resetAfterSec = Math.ceil(resetAfterMs / 1000);
+ if (offender.blocked) {
+ const global = bucket_id === "global";
+ // each block violation pushes the expiry one full window further
+ reset += opts.window * 1000;
+ offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
+ resetAfterMs = reset - Date.now();
+ resetAfterSec = Math.ceil(resetAfterMs / 1000);
- console.log(`blocked bucket: ${bucket_id} ${executor_id}`, {
- resetAfterMs,
- });
+ console.log(`blocked bucket: ${bucket_id} ${executor_id}`, {
+ resetAfterMs,
+ });
- if (global) res.set("X-RateLimit-Global", "true");
+ if (global) res.set("X-RateLimit-Global", "true");
- return (
- res
- .status(429)
- .set("X-RateLimit-Remaining", "0")
- .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
- // TODO: error rate limit message translation
- .send({
- message: "You are being rate limited.",
- retry_after: resetAfterSec,
- global,
- })
- );
- }
- }
+ return (
+ res
+ .status(429)
+ .set("X-RateLimit-Remaining", "0")
+ .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
+ // TODO: error rate limit message translation
+ .send({
+ message: "You are being rate limited.",
+ retry_after: resetAfterSec,
+ global,
+ })
+ );
+ }
+ }
- next();
- const hitRouteOpts = {
- bucket_id,
- executor_id,
- max_hits,
- window: opts.window,
- };
+ next();
+ const hitRouteOpts = {
+ bucket_id,
+ executor_id,
+ max_hits,
+ window: opts.window,
+ };
- if (opts.error || opts.success) {
- res.once("finish", () => {
- // check if error and increment error rate limit
- if (res.statusCode >= 400 && opts.error) {
- return hitRoute(hitRouteOpts);
- } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
- return hitRoute(hitRouteOpts);
- }
- });
- } else {
- return hitRoute(hitRouteOpts);
- }
- };
+ if (opts.error || opts.success) {
+ res.once("finish", () => {
+ // check if error and increment error rate limit
+ if (res.statusCode >= 400 && opts.error) {
+ return hitRoute(hitRouteOpts);
+ } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
+ return hitRoute(hitRouteOpts);
+ }
+ });
+ } else {
+ return hitRoute(hitRouteOpts);
+ }
+ };
}
export async function initRateLimits(app: Router) {
- const { routes, global, ip, error, enabled } = Config.get().limits.rate;
- if (!enabled) return;
- console.log("Enabling rate limits...");
- await listenEvent(EventRateLimit, (event) => {
- Cache.set(event.channel_id as string, event.data);
- event.acknowledge?.();
- });
- // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date
- // const limits = await RateLimit.find({ blocked: true });
- // limits.forEach((limit) => {
- // Cache.set(limit.executor_id, limit);
- // });
+ const { routes, global, ip, error, enabled } = Config.get().limits.rate;
+ if (!enabled) return;
+ console.log("Enabling rate limits...");
+ await listenEvent(EventRateLimit, (event) => {
+ Cache.set(event.channel_id as string, event.data);
+ event.acknowledge?.();
+ });
+ // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date
+ // const limits = await RateLimit.find({ blocked: true });
+ // limits.forEach((limit) => {
+ // Cache.set(limit.executor_id, limit);
+ // });
- setInterval(() => {
- Cache.forEach((x, key) => {
- if (new Date() > x.expires_at) {
- Cache.delete(key);
- // RateLimit.delete({ executor_id: key });
- }
- });
- }, 1000 * 60);
+ setInterval(() => {
+ Cache.forEach((x, key) => {
+ if (new Date() > x.expires_at) {
+ Cache.delete(key);
+ // RateLimit.delete({ executor_id: key });
+ }
+ });
+ }, 1000 * 60);
- app.use(
- rateLimit({
- bucket: "global",
- onlyIp: true,
- ...ip,
- }),
- );
- app.use(rateLimit({ bucket: "global", ...global }));
- app.use(
- rateLimit({
- bucket: "error",
- error: true,
- onlyIp: true,
- ...error,
- }),
- );
- app.use("/guilds/:guild_id", rateLimit(routes.guild));
- app.use("/webhooks/:webhook_id", rateLimit(routes.webhook));
- app.use("/channels/:channel_id", rateLimit(routes.channel));
- app.use("/auth/login", rateLimit(routes.auth.login));
- app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
+ app.use(
+ rateLimit({
+ bucket: "global",
+ onlyIp: true,
+ ...ip,
+ }),
+ );
+ app.use(rateLimit({ bucket: "global", ...global }));
+ app.use(
+ rateLimit({
+ bucket: "error",
+ error: true,
+ onlyIp: true,
+ ...error,
+ }),
+ );
+ app.use("/guilds/:guild_id", rateLimit(routes.guild));
+ app.use("/webhooks/:webhook_id", rateLimit(routes.webhook));
+ app.use("/channels/:channel_id", rateLimit(routes.channel));
+ app.use("/auth/login", rateLimit(routes.auth.login));
+ app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
}
async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) {
- const id = opts.executor_id + opts.bucket_id;
- let limit = Cache.get(id);
- if (!limit) {
- limit = {
- id: opts.bucket_id,
- executor_id: opts.executor_id,
- expires_at: new Date(Date.now() + opts.window * 1000),
- hits: 0,
- blocked: false,
- };
- Cache.set(id, limit);
- }
+ const id = opts.executor_id + opts.bucket_id;
+ let limit = Cache.get(id);
+ if (!limit) {
+ limit = {
+ id: opts.bucket_id,
+ executor_id: opts.executor_id,
+ expires_at: new Date(Date.now() + opts.window * 1000),
+ hits: 0,
+ blocked: false,
+ };
+ Cache.set(id, limit);
+ }
- limit.hits++;
- if (limit.hits >= opts.max_hits) {
- limit.blocked = true;
- }
+ limit.hits++;
+ if (limit.hits >= opts.max_hits) {
+ limit.blocked = true;
+ }
- /*
+ /*
let ratelimit = await RateLimit.findOne({ where: { id: opts.bucket_id, executor_id: opts.executor_id } });
if (!ratelimit) {
ratelimit = new RateLimit({
diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index e089407e2..3ca204b65 100644
--- a/src/api/middlewares/Translation.ts
+++ b/src/api/middlewares/Translation.ts
@@ -26,23 +26,23 @@ import { Router } from "express";
const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets");
export async function initTranslation(router: Router) {
- const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
- const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
- const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
+ const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
+ const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
+ const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
- await i18next
- .use(i18nextBackend)
- .use(i18nextMiddleware.LanguageDetector)
- .init({
- preload: languages,
- // debug: true,
- fallbackLng: "en",
- ns,
- backend: {
- loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
- },
- load: "all",
- });
+ await i18next
+ .use(i18nextBackend)
+ .use(i18nextMiddleware.LanguageDetector)
+ .init({
+ preload: languages,
+ // debug: true,
+ fallbackLng: "en",
+ ns,
+ backend: {
+ loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
+ },
+ load: "all",
+ });
- router.use(i18nextMiddleware.handle(i18next, {}));
+ router.use(i18nextMiddleware.handle(i18next, {}));
}
diff --git a/src/api/routes/-/healthz.ts b/src/api/routes/-/healthz.ts
index 5acd8c8cc..886473cf5 100644
--- a/src/api/routes/-/healthz.ts
+++ b/src/api/routes/-/healthz.ts
@@ -23,9 +23,9 @@ import { getDatabase } from "@spacebar/util";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- if (!getDatabase()) return res.sendStatus(503);
+ if (!getDatabase()) return res.sendStatus(503);
- return res.sendStatus(200);
+ return res.sendStatus(200);
});
export default router;
diff --git a/src/api/routes/-/readyz.ts b/src/api/routes/-/readyz.ts
index 5acd8c8cc..886473cf5 100644
--- a/src/api/routes/-/readyz.ts
+++ b/src/api/routes/-/readyz.ts
@@ -23,9 +23,9 @@ import { getDatabase } from "@spacebar/util";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- if (!getDatabase()) return res.sendStatus(503);
+ if (!getDatabase()) return res.sendStatus(503);
- return res.sendStatus(200);
+ return res.sendStatus(200);
});
export default router;
diff --git a/src/api/routes/applications/#application_id/bot/index.ts b/src/api/routes/applications/#application_id/bot/index.ts
index ad4918406..83b4fc61f 100644
--- a/src/api/routes/applications/#application_id/bot/index.ts
+++ b/src/api/routes/applications/#application_id/bot/index.ts
@@ -26,98 +26,98 @@ import { BotModifySchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {
- body: "TokenOnlyResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["owner"],
- });
+ "/",
+ route({
+ responses: {
+ 204: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["owner"],
+ });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- const user = await createAppBotUser(app, req);
+ const user = await createAppBotUser(app, req);
- res.send({
- token: await generateToken(user.id),
- });
- },
+ res.send({
+ token: await generateToken(user.id),
+ });
+ },
);
router.post(
- "/reset",
- route({
- responses: {
- 200: {
- body: "TokenResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const bot = await User.findOneOrFail({ where: { id: req.params.application_id } });
- const owner = await User.findOneOrFail({ where: { id: req.user_id } });
+ "/reset",
+ route({
+ responses: {
+ 200: {
+ body: "TokenResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const bot = await User.findOneOrFail({ where: { id: req.params.application_id } });
+ const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- bot.data = { hash: undefined, valid_tokens_since: new Date() };
+ bot.data = { hash: undefined, valid_tokens_since: new Date() };
- await bot.save();
+ await bot.save();
- const token = await generateToken(bot.id);
+ const token = await generateToken(bot.id);
- res.json({ token }).status(200);
- },
+ res.json({ token }).status(200);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "BotModifySchema",
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as BotModifySchema;
- if (!body.avatar?.trim()) delete body.avatar;
+ "/",
+ route({
+ requestBody: "BotModifySchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as BotModifySchema;
+ if (!body.avatar?.trim()) delete body.avatar;
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["bot", "owner"],
- });
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["bot", "owner"],
+ });
- if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
+ if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string);
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string);
- app.bot.assign(body);
+ app.bot.assign(body);
- app.bot.save();
+ app.bot.save();
- await app.save();
- res.json(app).status(200);
- },
+ await app.save();
+ res.json(app).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/commands/#command_id/index.ts b/src/api/routes/applications/#application_id/commands/#command_id/index.ts
index 7907c1ba7..8e4c3ce9d 100644
--- a/src/api/routes/applications/#application_id/commands/#command_id/index.ts
+++ b/src/api/routes/applications/#application_id/commands/#command_id/index.ts
@@ -24,99 +24,99 @@ import { Application, ApplicationCommand, FieldErrors, Snowflake } from "@spaceb
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } });
+ const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } });
- if (!command) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!command) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- res.send(command);
+ res.send(command);
});
router.patch(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- await ApplicationCommand.update({ name: body.name.trim() }, commandForDb);
- res.send(commandForDb);
- },
+ await ApplicationCommand.update({ name: body.name.trim() }, commandForDb);
+ res.send(commandForDb);
+ },
);
router.delete("/", async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id });
- res.sendStatus(204);
+ await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id });
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/applications/#application_id/commands/index.ts b/src/api/routes/applications/#application_id/commands/index.ts
index 97753f007..91824be83 100644
--- a/src/api/routes/applications/#application_id/commands/index.ts
+++ b/src/api/routes/applications/#application_id/commands/index.ts
@@ -25,147 +25,147 @@ import { IsNull } from "typeorm";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } });
- res.send(command);
+ const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } });
+ res.send(command);
});
router.post(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
router.put(
- "/",
- route({
- requestBody: "BulkApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "BulkApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema[];
+ const body = req.body as ApplicationCommandCreateSchema[];
- // Remove commands not present in array
- const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } });
+ // Remove commands not present in array
+ const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } });
- const commandNamesInArray = body.map((c) => c.name);
- const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
+ const commandNamesInArray = body.map((c) => c.name);
+ const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
- for (const command of commandsNotInArray) {
- await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id });
- }
+ for (const command of commandsNotInArray) {
+ await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id });
+ }
- for (const command of body) {
- if (!command.type) {
- command.type = 1;
- }
+ for (const command of body) {
+ if (!command.type) {
+ command.type = 1;
+ }
- if (command.name.trim().length < 1 || command.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (command.name.trim().length < 1 || command.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: command.name.trim(),
- name_localizations: command.name_localizations,
- description: command.description?.trim() || "",
- description_localizations: command.description_localizations,
- default_member_permissions: command.default_member_permissions || null,
- contexts: command.contexts,
- dm_permission: command.dm_permission || true,
- global_popularity_rank: 1,
- handler: command.handler,
- integration_types: command.integration_types,
- nsfw: command.nsfw,
- options: command.options,
- type: command.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: command.name.trim(),
+ name_localizations: command.name_localizations,
+ description: command.description?.trim() || "",
+ description_localizations: command.description_localizations,
+ default_member_permissions: command.default_member_permissions || null,
+ contexts: command.contexts,
+ dm_permission: command.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: command.handler,
+ integration_types: command.integration_types,
+ nsfw: command.nsfw,
+ options: command.options,
+ type: command.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/entitlements.ts b/src/api/routes/applications/#application_id/entitlements.ts
index 63a7e7b9b..4cd13b938 100644
--- a/src/api/routes/applications/#application_id/entitlements.ts
+++ b/src/api/routes/applications/#application_id/entitlements.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationEntitlementsResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- //const { exclude_consumed } = req.query;
- res.status(200).send([]);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationEntitlementsResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ //const { exclude_consumed } = req.query;
+ res.status(200).send([]);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
index 2570358d7..be2d62e79 100644
--- a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
+++ b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
@@ -24,140 +24,140 @@ import { Application, ApplicationCommand, FieldErrors, Guild, Member, Snowflake
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } });
+ const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } });
- if (!command) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!command) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- res.send(command);
+ res.send(command);
});
router.patch(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({
- where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
- });
+ const commandExists = await ApplicationCommand.exists({
+ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
+ });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- await ApplicationCommand.update(
- { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
- commandForDb,
- );
- res.send(commandForDb);
- },
+ await ApplicationCommand.update(
+ { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
+ commandForDb,
+ );
+ res.send(commandForDb);
+ },
);
router.delete("/", async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id });
- res.sendStatus(204);
+ await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id });
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
index 1639e7be1..607c846c9 100644
--- a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
+++ b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
@@ -24,185 +24,185 @@ import { Application, ApplicationCommand, FieldErrors, Guild, Member, Snowflake
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
- res.send(command);
+ const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
+ res.send(command);
});
router.post(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- guild_id: req.params.guild_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ guild_id: req.params.guild_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
router.put(
- "/",
- route({
- requestBody: "BulkApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "BulkApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema[];
+ const body = req.body as ApplicationCommandCreateSchema[];
- // Remove commands not present in array
- const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
+ // Remove commands not present in array
+ const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
- const commandNamesInArray = body.map((c) => c.name);
- const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
+ const commandNamesInArray = body.map((c) => c.name);
+ const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
- for (const command of commandsNotInArray) {
- await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id });
- }
+ for (const command of commandsNotInArray) {
+ await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id });
+ }
- for (const command of body) {
- if (!command.type) {
- command.type = 1;
- }
+ for (const command of body) {
+ if (!command.type) {
+ command.type = 1;
+ }
- if (command.name.trim().length < 1 || command.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (command.name.trim().length < 1 || command.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- guild_id: req.params.guild_id,
- name: command.name.trim(),
- name_localizations: command.name_localizations,
- description: command.description?.trim() || "",
- description_localizations: command.description_localizations,
- default_member_permissions: command.default_member_permissions || null,
- contexts: command.contexts,
- dm_permission: command.dm_permission || true,
- global_popularity_rank: 1,
- handler: command.handler,
- integration_types: command.integration_types,
- nsfw: command.nsfw,
- options: command.options,
- type: command.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ guild_id: req.params.guild_id,
+ name: command.name.trim(),
+ name_localizations: command.name_localizations,
+ description: command.description?.trim() || "",
+ description_localizations: command.description_localizations,
+ default_member_permissions: command.default_member_permissions || null,
+ contexts: command.contexts,
+ dm_permission: command.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: command.handler,
+ integration_types: command.integration_types,
+ nsfw: command.nsfw,
+ options: command.options,
+ type: command.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/index.ts b/src/api/routes/applications/#application_id/index.ts
index c38895d21..bf29f4043 100644
--- a/src/api/routes/applications/#application_id/index.ts
+++ b/src/api/routes/applications/#application_id/index.ts
@@ -26,106 +26,106 @@ import { ApplicationModifySchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["owner", "bot"],
- });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["owner", "bot"],
+ });
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- return res.json(app);
- },
+ return res.json(app);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ApplicationModifySchema",
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationModifySchema;
+ "/",
+ route({
+ requestBody: "ApplicationModifySchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationModifySchema;
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["owner", "bot"],
- });
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["owner", "bot"],
+ });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- if (body.icon) {
- body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
- }
- if (body.cover_image) {
- body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
- }
+ if (body.icon) {
+ body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
+ }
+ if (body.cover_image) {
+ body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
+ }
- if (body.guild_id) {
- const guild = await Guild.findOneOrFail({
- where: { id: body.guild_id },
- select: ["owner_id"],
- });
- if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
- }
+ if (body.guild_id) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: body.guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
+ }
- if (app.bot) {
- app.bot.assign({ bio: body.description });
- await app.bot.save();
- }
+ if (app.bot) {
+ app.bot.assign({ bio: body.description });
+ await app.bot.save();
+ }
- app.assign(body);
+ app.assign(body);
- await app.save();
+ await app.save();
- return res.json(app);
- },
+ return res.json(app);
+ },
);
router.post(
- "/delete",
- route({
- responses: {
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["bot", "owner"],
- });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ "/delete",
+ route({
+ responses: {
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["bot", "owner"],
+ });
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- if (app.bot) {
- await User.delete({ id: app.id });
- }
- await Application.delete({ id: app.id });
+ if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (app.bot) {
+ await User.delete({ id: app.id });
+ }
+ await Application.delete({ id: app.id });
- res.send().status(200);
- },
+ res.send().status(200);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/skus.ts b/src/api/routes/applications/#application_id/skus.ts
index 0877f4fbd..9b9e3387e 100644
--- a/src/api/routes/applications/#application_id/skus.ts
+++ b/src/api/routes/applications/#application_id/skus.ts
@@ -22,17 +22,17 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationSkusResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json([]).status(200);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationSkusResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json([]).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/applications/@me.ts b/src/api/routes/applications/@me.ts
index f4a909762..c1cf0db03 100644
--- a/src/api/routes/applications/@me.ts
+++ b/src/api/routes/applications/@me.ts
@@ -27,74 +27,74 @@ const router: Router = Router({ mergeParams: true });
// TODO: actually make this be correct - this is just a copy paste of /applications/:id/index.ts minus delete
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.user_id },
- relations: ["owner", "bot"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["owner", "bot"],
+ });
- return res.json(app);
- },
+ return res.json(app);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ApplicationModifySchema",
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationModifySchema;
+ "/",
+ route({
+ requestBody: "ApplicationModifySchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationModifySchema;
- const app = await Application.findOneOrFail({
- where: { id: req.user_id },
- relations: ["owner", "bot"],
- });
+ const app = await Application.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["owner", "bot"],
+ });
- if (body.icon) {
- body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
- }
- if (body.cover_image) {
- body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
- }
+ if (body.icon) {
+ body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
+ }
+ if (body.cover_image) {
+ body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
+ }
- if (body.guild_id) {
- const guild = await Guild.findOneOrFail({
- where: { id: body.guild_id },
- select: ["owner_id"],
- });
- if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
- }
+ if (body.guild_id) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: body.guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
+ }
- if (app.bot) {
- app.bot.assign({ bio: body.description });
- await app.bot.save();
- }
+ if (app.bot) {
+ app.bot.assign({ bio: body.description });
+ await app.bot.save();
+ }
- app.assign(body);
+ app.assign(body);
- await app.save();
+ await app.save();
- return res.json(app);
- },
+ return res.json(app);
+ },
);
export default router;
diff --git a/src/api/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts
index 4de8a78d2..83122e761 100644
--- a/src/api/routes/applications/detectable.ts
+++ b/src/api/routes/applications/detectable.ts
@@ -22,32 +22,32 @@ import { ApplicationDetectableResponse } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
const cache = {
- data: {},
- expires: 0,
+ data: {},
+ expires: 0,
};
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationDetectableResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // cache for 6 hours
- if (Date.now() > cache.expires) {
- const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
- const data = await response.json();
- cache.data = data as ApplicationDetectableResponse;
- cache.expires = Date.now() + 6 * 60 * 60 * 1000;
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationDetectableResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // cache for 6 hours
+ if (Date.now() > cache.expires) {
+ const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
+ const data = await response.json();
+ cache.data = data as ApplicationDetectableResponse;
+ cache.expires = Date.now() + 6 * 60 * 60 * 1000;
+ }
- res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
- .status(200)
- .json(cache.data);
- },
+ res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
+ .status(200)
+ .json(cache.data);
+ },
);
export default router;
diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts
index e3b270c32..ff89f03d0 100644
--- a/src/api/routes/applications/index.ts
+++ b/src/api/routes/applications/index.ts
@@ -24,54 +24,54 @@ import { ApplicationCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIApplicationArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const results = await Application.find({
- where: { owner: { id: req.user_id } },
- relations: ["owner", "bot"],
- });
- res.json(results).status(200);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIApplicationArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const results = await Application.find({
+ where: { owner: { id: req.user_id } },
+ relations: ["owner", "bot"],
+ });
+ res.json(results).status(200);
+ },
);
router.post(
- "/",
- route({
- requestBody: "ApplicationCreateSchema",
- responses: {
- 200: {
- body: "Application",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationCreateSchema;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCreateSchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationCreateSchema;
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const app = Application.create({
- name: trimSpecial(body.name),
- description: "",
- bot_public: true,
- owner: user,
- verify_key: "IMPLEMENTME",
- flags: 0,
- });
+ const app = Application.create({
+ name: trimSpecial(body.name),
+ description: "",
+ bot_public: true,
+ owner: user,
+ verify_key: "IMPLEMENTME",
+ flags: 0,
+ });
- // april 14, 2023: discord made bot users be automatically added to all new apps
- const { autoCreateBotUsers } = Config.get().general;
- if (autoCreateBotUsers) {
- await createAppBotUser(app, req);
- } else await app.save();
+ // april 14, 2023: discord made bot users be automatically added to all new apps
+ const { autoCreateBotUsers } = Config.get().general;
+ if (autoCreateBotUsers) {
+ await createAppBotUser(app, req);
+ } else await app.save();
- res.json(app);
- },
+ res.json(app);
+ },
);
export default router;
diff --git a/src/api/routes/attachments/refresh-urls.ts b/src/api/routes/attachments/refresh-urls.ts
index 92a7dd165..f1530914c 100644
--- a/src/api/routes/attachments/refresh-urls.ts
+++ b/src/api/routes/attachments/refresh-urls.ts
@@ -23,37 +23,37 @@ import { RefreshUrlsRequestSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "RefreshUrlsRequestSchema",
- responses: {
- 200: {
- body: "RefreshUrlsResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { attachment_urls } = req.body as RefreshUrlsRequestSchema;
+ "/",
+ route({
+ requestBody: "RefreshUrlsRequestSchema",
+ responses: {
+ 200: {
+ body: "RefreshUrlsResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { attachment_urls } = req.body as RefreshUrlsRequestSchema;
- const refreshed_urls = attachment_urls.map((url) => {
- return getUrlSignature(
- new NewUrlSignatureData({
- url: url,
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- )
- .applyToUrl(url)
- .toString();
- });
+ const refreshed_urls = attachment_urls.map((url) => {
+ return getUrlSignature(
+ new NewUrlSignatureData({
+ url: url,
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ )
+ .applyToUrl(url)
+ .toString();
+ });
- return res.status(200).json({
- refreshed_urls,
- });
- },
+ return res.status(200).json({
+ refreshed_urls,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/fingerprint.ts b/src/api/routes/auth/fingerprint.ts
index d7ef34b4f..a4a49d079 100644
--- a/src/api/routes/auth/fingerprint.ts
+++ b/src/api/routes/auth/fingerprint.ts
@@ -21,9 +21,9 @@ import { Snowflake } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post("/", route({ responses: { 200: { body: "CreateFingerprintResponse" } } }), (req: Request, res: Response) => {
- const snowflake = Snowflake.generate();
- return res.json({
- fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`,
- });
+ const snowflake = Snowflake.generate();
+ return res.json({
+ fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`,
+ });
});
export default router;
diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts
index 6e613820f..db16c3be9 100644
--- a/src/api/routes/auth/forgot.ts
+++ b/src/api/routes/auth/forgot.ts
@@ -23,55 +23,55 @@ import { ForgotPasswordSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "ForgotPasswordSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { login, captcha_key } = req.body as ForgotPasswordSchema;
+ "/",
+ route({
+ requestBody: "ForgotPasswordSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { login, captcha_key } = req.body as ForgotPasswordSchema;
- const config = Config.get();
+ const config = Config.get();
- if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const ip = req.ip;
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const ip = req.ip;
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- res.sendStatus(204);
+ res.sendStatus(204);
- const user = await User.findOne({
- where: [{ phone: login }, { email: login }],
- select: ["username", "id", "email"],
- }).catch(() => {});
+ const user = await User.findOne({
+ where: [{ phone: login }, { email: login }],
+ select: ["username", "id", "email"],
+ }).catch(() => {});
- if (user && user.email) {
- Email.sendResetPassword(user, user.email).catch((e) => {
- console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`);
- });
- }
- },
+ if (user && user.email) {
+ Email.sendResetPassword(user, user.email).catch((e) => {
+ console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`);
+ });
+ }
+ },
);
export default router;
diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts
index be9329530..a6b70c65b 100644
--- a/src/api/routes/auth/generate-registration-tokens.ts
+++ b/src/api/routes/auth/generate-registration-tokens.ts
@@ -24,46 +24,46 @@ const router: Router = Router({ mergeParams: true });
export default router;
router.get(
- "/",
- route({
- query: {
- count: {
- type: "number",
- description: "The number of registration tokens to generate. Defaults to 1.",
- },
- length: {
- type: "number",
- description: "The length of each registration token. Defaults to 255.",
- },
- },
- right: "CREATE_REGISTRATION_TOKENS",
- responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
- }),
- async (req: Request, res: Response) => {
- const count = req.query.count ? parseInt(req.query.count as string) : 1;
- const length = req.query.length ? parseInt(req.query.length as string) : 255;
+ "/",
+ route({
+ query: {
+ count: {
+ type: "number",
+ description: "The number of registration tokens to generate. Defaults to 1.",
+ },
+ length: {
+ type: "number",
+ description: "The length of each registration token. Defaults to 255.",
+ },
+ },
+ right: "CREATE_REGISTRATION_TOKENS",
+ responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
+ }),
+ async (req: Request, res: Response) => {
+ const count = req.query.count ? parseInt(req.query.count as string) : 1;
+ const length = req.query.length ? parseInt(req.query.length as string) : 255;
- const tokens: ValidRegistrationToken[] = [];
+ const tokens: ValidRegistrationToken[] = [];
- for (let i = 0; i < count; i++) {
- const token = ValidRegistrationToken.create({
- token: randomString(length),
- expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration),
- });
- tokens.push(token);
- }
+ for (let i = 0; i < count; i++) {
+ const token = ValidRegistrationToken.create({
+ token: randomString(length),
+ expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration),
+ });
+ tokens.push(token);
+ }
- // Why are these options used, exactly?
- await ValidRegistrationToken.save(tokens, {
- chunk: 1000,
- reload: false,
- transaction: false,
- });
+ // Why are these options used, exactly?
+ await ValidRegistrationToken.save(tokens, {
+ chunk: 1000,
+ reload: false,
+ transaction: false,
+ });
- const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token);
+ const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token);
- if (req.query.plain) return res.send(ret.join("\n"));
+ if (req.query.plain) return res.send(ret.join("\n"));
- return res.json({ tokens: ret });
- },
+ return res.json({ tokens: ret });
+ },
);
diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index 92f47909d..6f2f26af1 100644
--- a/src/api/routes/auth/location-metadata.ts
+++ b/src/api/routes/auth/location-metadata.ts
@@ -22,24 +22,24 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "LocationMetadataResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- //TODO
- //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
- const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code;
- res.json({
- consent_required: false,
- country_code: country_code,
- promotional_email_opt_in: { required: true, pre_checked: false },
- });
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "LocationMetadataResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ //TODO
+ //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
+ const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code;
+ res.json({
+ consent_required: false,
+ country_code: country_code,
+ promotional_email_opt_in: { required: true, pre_checked: false },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts
index c8ef62731..911ad4a96 100644
--- a/src/api/routes/auth/login.ts
+++ b/src/api/routes/auth/login.ts
@@ -27,157 +27,157 @@ const router: Router = Router({ mergeParams: true });
export default router;
router.post(
- "/",
- route({
- requestBody: "LoginSchema",
- responses: {
- 200: {
- body: "LoginResponse",
- },
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { login, password, captcha_key, undelete } = req.body as LoginSchema;
+ "/",
+ route({
+ requestBody: "LoginSchema",
+ responses: {
+ 200: {
+ body: "LoginResponse",
+ },
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { login, password, captcha_key, undelete } = req.body as LoginSchema;
- const config = Config.get();
+ const config = Config.get();
- if (config.login.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (config.login.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const ip = req.ip;
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const ip = req.ip;
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- const user = await User.findOneOrFail({
- where: [{ phone: login }, { email: login }],
- select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"],
- relations: ["security_keys", "settings"],
- }).catch(() => {
- throw FieldErrors({
- login: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- password: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- });
- });
+ const user = await User.findOneOrFail({
+ where: [{ phone: login }, { email: login }],
+ select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"],
+ relations: ["security_keys", "settings"],
+ }).catch(() => {
+ throw FieldErrors({
+ login: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ password: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ });
+ });
- // 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({
- login: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- password: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- });
- }
+ // 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({
+ login: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ password: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ });
+ }
- // return an error for unverified accounts if verification is required
- if (config.login.requireVerification && !user.verified) {
- throw FieldErrors({
- login: {
- code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
- message: "Email verification is required, please check your email.",
- },
- });
- }
+ // return an error for unverified accounts if verification is required
+ if (config.login.requireVerification && !user.verified) {
+ throw FieldErrors({
+ login: {
+ code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
+ message: "Email verification is required, please check your email.",
+ },
+ });
+ }
- if (user.mfa_enabled && !user.webauthn_enabled) {
- // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
- const ticket = crypto.randomBytes(40).toString("hex");
+ if (user.mfa_enabled && !user.webauthn_enabled) {
+ // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
+ const ticket = crypto.randomBytes(40).toString("hex");
- await User.update({ id: user.id }, { totp_last_ticket: ticket });
+ await User.update({ id: user.id }, { totp_last_ticket: ticket });
- return res.json({
- ticket: ticket,
- mfa: true,
- sms: false, // TODO
- token: null,
- });
- }
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
+ });
+ }
- if (user.mfa_enabled && user.webauthn_enabled) {
- if (!WebAuthn.fido2) {
- // TODO: I did this for typescript and I can't use !
- throw new Error("WebAuthn not enabled");
- }
+ if (user.mfa_enabled && user.webauthn_enabled) {
+ if (!WebAuthn.fido2) {
+ // TODO: I did this for typescript and I can't use !
+ throw new Error("WebAuthn not enabled");
+ }
- const options = await WebAuthn.fido2.assertionOptions();
- const challenge = JSON.stringify({
- publicKey: {
- ...options,
- challenge: Buffer.from(options.challenge).toString("base64"),
- allowCredentials: user.security_keys.map((x) => ({
- id: x.key_id,
- type: "public-key",
- })),
- transports: ["usb", "ble", "nfc"],
- timeout: 60000,
- },
- });
+ const options = await WebAuthn.fido2.assertionOptions();
+ const challenge = JSON.stringify({
+ publicKey: {
+ ...options,
+ challenge: Buffer.from(options.challenge).toString("base64"),
+ allowCredentials: user.security_keys.map((x) => ({
+ id: x.key_id,
+ type: "public-key",
+ })),
+ transports: ["usb", "ble", "nfc"],
+ timeout: 60000,
+ },
+ });
- const ticket = await generateWebAuthnTicket(challenge);
- await User.update({ id: user.id }, { totp_last_ticket: ticket });
+ const ticket = await generateWebAuthnTicket(challenge);
+ await User.update({ id: user.id }, { totp_last_ticket: ticket });
- return res.json({
- ticket: ticket,
- mfa: true,
- sms: false, // TODO
- token: null,
- webauthn: challenge,
- });
- }
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
+ webauthn: challenge,
+ });
+ }
- 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,
- });
- }
+ 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,
+ });
+ }
- const token = await generateToken(user.id);
+ 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
+ // 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, index: undefined } });
- },
+ res.json({ token, settings: { ...user.settings, index: undefined } });
+ },
);
/**
diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts
index f20a19942..b460e31a9 100644
--- a/src/api/routes/auth/logout.ts
+++ b/src/api/routes/auth/logout.ts
@@ -24,25 +24,25 @@ const router: Router = Router({ mergeParams: true });
export default router;
router.post(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- if (req.body.provider != null || req.body.voip_provider != null) {
- console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
- } else {
- delete req.body.provider;
- delete req.body.voip_provider;
- if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
- }
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (req.body.provider != null || req.body.voip_provider != null) {
+ console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
+ } else {
+ delete req.body.provider;
+ delete req.body.voip_provider;
+ if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
+ }
- if (req.token.did) {
- await Session.delete({ user_id: req.user_id, session_id: req.token.did });
- }
+ if (req.token.did) {
+ await Session.delete({ user_id: req.user_id, session_id: req.token.did });
+ }
- res.status(204).send();
- },
+ res.status(204).send();
+ },
);
diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts
index edf307cc2..b9f2fa85d 100644
--- a/src/api/routes/auth/mfa/totp.ts
+++ b/src/api/routes/auth/mfa/totp.ts
@@ -25,54 +25,54 @@ import { TotpSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "TotpSchema",
- responses: {
- 200: {
- body: "TokenResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { code, ticket, gift_code_sku_id, login_source } =
- const { code, ticket } = req.body as TotpSchema;
+ "/",
+ route({
+ requestBody: "TotpSchema",
+ responses: {
+ 200: {
+ body: "TokenResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { code, ticket, gift_code_sku_id, login_source } =
+ const { code, ticket } = req.body as TotpSchema;
- const user = await User.findOneOrFail({
- where: {
- totp_last_ticket: ticket,
- },
- select: ["id", "totp_secret"],
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ totp_last_ticket: ticket,
+ },
+ select: ["id", "totp_secret"],
+ relations: ["settings"],
+ });
- const backup = await BackupCode.findOne({
- where: {
- code: code,
- expired: false,
- consumed: false,
- user: { id: user.id },
- },
- });
+ const backup = await BackupCode.findOne({
+ where: {
+ code: code,
+ expired: false,
+ consumed: false,
+ user: { id: user.id },
+ },
+ });
- if (!backup) {
- const ret = verifyToken(user.totp_secret || "", code);
- if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- } else {
- backup.consumed = true;
- await backup.save();
- }
+ if (!backup) {
+ const ret = verifyToken(user.totp_secret || "", code);
+ if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ } else {
+ backup.consumed = true;
+ await backup.save();
+ }
- await User.update({ id: user.id }, { totp_last_ticket: "" });
+ await User.update({ id: user.id }, { totp_last_ticket: "" });
- return res.json({
- token: await generateToken(user.id),
- settings: { ...user.settings, index: undefined },
- });
- },
+ return res.json({
+ token: await generateToken(user.id),
+ settings: { ...user.settings, index: undefined },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/mfa/webauthn.ts b/src/api/routes/auth/mfa/webauthn.ts
index 63b1335d9..da476ab42 100644
--- a/src/api/routes/auth/mfa/webauthn.ts
+++ b/src/api/routes/auth/mfa/webauthn.ts
@@ -25,77 +25,77 @@ import { WebAuthnTotpSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
function toArrayBuffer(buf: Buffer) {
- const ab = new ArrayBuffer(buf.length);
- const view = new Uint8Array(ab);
- for (let i = 0; i < buf.length; ++i) {
- view[i] = buf[i];
- }
- return ab;
+ const ab = new ArrayBuffer(buf.length);
+ const view = new Uint8Array(ab);
+ for (let i = 0; i < buf.length; ++i) {
+ view[i] = buf[i];
+ }
+ return ab;
}
router.post(
- "/",
- route({
- requestBody: "WebAuthnTotpSchema",
- responses: {
- 200: { body: "TokenResponse" },
- 400: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- if (!WebAuthn.fido2) {
- // TODO: I did this for typescript and I can't use !
- throw new Error("WebAuthn not enabled");
- }
+ "/",
+ route({
+ requestBody: "WebAuthnTotpSchema",
+ responses: {
+ 200: { body: "TokenResponse" },
+ 400: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!WebAuthn.fido2) {
+ // TODO: I did this for typescript and I can't use !
+ throw new Error("WebAuthn not enabled");
+ }
- const { code, ticket } = req.body as WebAuthnTotpSchema;
+ const { code, ticket } = req.body as WebAuthnTotpSchema;
- const user = await User.findOneOrFail({
- where: {
- totp_last_ticket: ticket,
- },
- select: ["id"],
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ totp_last_ticket: ticket,
+ },
+ select: ["id"],
+ relations: ["settings"],
+ });
- const ret = await verifyWebAuthnToken(ticket);
- if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ const ret = await verifyWebAuthnToken(ticket);
+ if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- await User.update({ id: user.id }, { totp_last_ticket: "" });
+ await User.update({ id: user.id }, { totp_last_ticket: "" });
- const clientAttestationResponse = JSON.parse(code);
+ const clientAttestationResponse = JSON.parse(code);
- if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
+ if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
- clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url"));
+ clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url"));
- const securityKey = await SecurityKey.findOneOrFail({
- where: {
- key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"),
- },
- });
+ const securityKey = await SecurityKey.findOneOrFail({
+ where: {
+ key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"),
+ },
+ });
- const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
+ const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
- const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, {
- ...assertionExpectations,
- factor: "second",
- publicKey: securityKey.public_key,
- prevCounter: securityKey.counter,
- userHandle: securityKey.key_id,
- });
+ const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, {
+ ...assertionExpectations,
+ factor: "second",
+ publicKey: securityKey.public_key,
+ prevCounter: securityKey.counter,
+ userHandle: securityKey.key_id,
+ });
- const counter = authnResult.authnrData.get("counter");
+ const counter = authnResult.authnrData.get("counter");
- securityKey.counter = counter;
+ securityKey.counter = counter;
- await securityKey.save();
+ await securityKey.save();
- return res.json({
- token: await generateToken(user.id),
- user_settings: user.settings,
- });
- },
+ return res.json({
+ token: await generateToken(user.id),
+ user_settings: user.settings,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index d4b061be2..6aa52bffe 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -27,294 +27,294 @@ import { RegisterSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "RegisterSchema",
- responses: {
- 200: { body: "TokenOnlyResponse" },
- 400: { body: "APIErrorOrCaptchaResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as RegisterSchema;
- const { register, security, limits } = Config.get();
- const ip = req.ip!;
+ "/",
+ route({
+ requestBody: "RegisterSchema",
+ responses: {
+ 200: { body: "TokenOnlyResponse" },
+ 400: { body: "APIErrorOrCaptchaResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as RegisterSchema;
+ const { register, security, limits } = Config.get();
+ const ip = req.ip!;
- // Reg tokens
- // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
- let regTokenUsed = false;
- if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
- // eg theyre on https://staging.spacebar.chat/register?token=whatever
- const token = req.get("Referrer")?.split("token=")[1].split("&")[0];
- if (token) {
- const regToken = await ValidRegistrationToken.findOneOrFail({
- where: { token, expires_at: MoreThan(new Date()) },
- });
- await regToken.remove();
- regTokenUsed = true;
- console.log(`[REGISTER] Registration token ${token} used for registration!`);
- } else {
- console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
- }
- }
+ // Reg tokens
+ // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
+ let regTokenUsed = false;
+ if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
+ // eg theyre on https://staging.spacebar.chat/register?token=whatever
+ const token = req.get("Referrer")?.split("token=")[1].split("&")[0];
+ if (token) {
+ const regToken = await ValidRegistrationToken.findOneOrFail({
+ where: { token, expires_at: MoreThan(new Date()) },
+ });
+ await regToken.remove();
+ regTokenUsed = true;
+ console.log(`[REGISTER] Registration token ${token} used for registration!`);
+ } else {
+ console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
+ }
+ }
- // check if registration is allowed
- if (!regTokenUsed && !register.allowNewRegistration) {
- throw FieldErrors({
- email: {
- code: "REGISTRATION_DISABLED",
- message: req.t("auth:register.REGISTRATION_DISABLED"),
- },
- });
- }
+ // check if registration is allowed
+ if (!regTokenUsed && !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 (!body.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 (!body.consent) {
+ throw FieldErrors({
+ consent: {
+ code: "CONSENT_REQUIRED",
+ message: req.t("auth:register.CONSENT_REQUIRED"),
+ },
+ });
+ }
- if (!regTokenUsed && register.disabled) {
- throw FieldErrors({
- email: {
- code: "DISABLED",
- message: "registration is disabled on this instance",
- },
- });
- }
+ if (!regTokenUsed && register.disabled) {
+ throw FieldErrors({
+ email: {
+ code: "DISABLED",
+ message: "registration is disabled on this instance",
+ },
+ });
+ }
- if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
- const { sitekey, service } = security.captcha;
- if (!body.captcha_key) {
- return res?.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
+ const { sitekey, service } = security.captcha;
+ if (!body.captcha_key) {
+ return res?.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const verify = await verifyCaptcha(body.captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const verify = await verifyCaptcha(body.captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- if (!regTokenUsed && !register.allowMultipleAccounts) {
- // TODO: check if fingerprint was eligible generated
- const exists = await User.findOne({
- where: { fingerprints: body.fingerprint },
- select: ["id"],
- });
+ if (!regTokenUsed && !register.allowMultipleAccounts) {
+ // TODO: check if fingerprint was eligible generated
+ const exists = await User.findOne({
+ where: { fingerprints: body.fingerprint },
+ select: ["id"],
+ });
- if (exists) {
- throw FieldErrors({
- email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
- },
- });
- }
- }
+ if (exists) {
+ throw FieldErrors({
+ email: {
+ code: "EMAIL_ALREADY_REGISTERED",
+ message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
+ },
+ });
+ }
+ }
- if (!regTokenUsed && register.checkIp) {
- const blacklist = await AbuseIpDbClient.getBlacklist();
- if (blacklist) {
- const entry = blacklist.data.find((e) => e.ipAddress === ip);
+ if (!regTokenUsed && register.checkIp) {
+ const blacklist = await AbuseIpDbClient.getBlacklist();
+ if (blacklist) {
+ const entry = blacklist.data.find((e) => e.ipAddress === ip);
- if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
- console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`);
- throw new HTTPError("Your IP is blocked from registration");
- }
- }
+ if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
+ console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
+ }
- const checkIp = await AbuseIpDbClient.checkIpAddress(ip);
- if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
- console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ const checkIp = await AbuseIpDbClient.checkIpAddress(ip);
+ if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
+ console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- const ipData = await IpDataClient.getIpInfo(ip);
- if (ipData) {
- if (!ipData.threat) {
- console.log("Invalid IPData.co response, missing threat field", ipData);
- }
- const categories = Object.entries(ipData.threat)
- .filter(([key, value]) => key.startsWith("is_") && value === true)
- .map(([key]) => key.replace("is_", ""));
- const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes));
- if (blockedCategories.size > 0) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ const ipData = await IpDataClient.getIpInfo(ip);
+ if (ipData) {
+ if (!ipData.threat) {
+ console.log("Invalid IPData.co response, missing threat field", ipData);
+ }
+ const categories = Object.entries(ipData.threat)
+ .filter(([key, value]) => key.startsWith("is_") && value === true)
+ .map(([key]) => key.replace("is_", ""));
+ const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes));
+ if (blockedCategories.size > 0) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- if (register.blockAsnTypes.includes(ipData.asn.type)) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ if (register.blockAsnTypes.includes(ipData.asn.type)) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- if (register.blockAsns.includes(ipData.asn.asn)) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ if (register.blockAsns.includes(ipData.asn.asn)) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- if (register.blockProxies && IpDataClient.isProxy(ipData)) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`);
- throw new HTTPError("Your IP is blocked from registration");
- }
- }
- }
+ if (register.blockProxies && IpDataClient.isProxy(ipData)) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
+ }
+ }
- // TODO: gift_code_sku_id?
- // TODO: check password strength
+ // TODO: gift_code_sku_id?
+ // TODO: check password strength
- const email = body.email;
- 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"),
- },
- });
- }
+ const email = body.email;
+ 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"),
+ },
+ });
+ }
- // check if there is already an account with this email
- const exists = await User.findOne({ where: { email: email } });
+ // check if there is already an account with this email
+ const exists = await User.findOne({ where: { email: email } });
- if (exists) {
- throw FieldErrors({
- email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
- },
- });
- }
- } else if (register.email.required) {
- throw FieldErrors({
- email: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (exists) {
+ throw FieldErrors({
+ email: {
+ code: "EMAIL_ALREADY_REGISTERED",
+ message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
+ },
+ });
+ }
+ } else if (register.email.required) {
+ throw FieldErrors({
+ email: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- if (register.dateOfBirth.required && !body.date_of_birth) {
- throw FieldErrors({
- date_of_birth: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
- const minimum = new Date();
- minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
+ if (register.dateOfBirth.required && !body.date_of_birth) {
+ throw FieldErrors({
+ date_of_birth: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
+ const minimum = new Date();
+ minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
- let parsedDob;
- try {
- parsedDob = new Date(body.date_of_birth as Date);
- if (isNaN(parsedDob.getTime())) {
- throw new Error("Invalid date");
- }
- } catch (e) {
- throw FieldErrors({
- date_of_birth: {
- code: "DATE_OF_BIRTH_INVALID",
- message: req.t("auth:register.DATE_OF_BIRTH_INVALID"),
- },
- });
- }
+ let parsedDob;
+ try {
+ parsedDob = new Date(body.date_of_birth as Date);
+ if (isNaN(parsedDob.getTime())) {
+ throw new Error("Invalid date");
+ }
+ } catch (e) {
+ throw FieldErrors({
+ date_of_birth: {
+ code: "DATE_OF_BIRTH_INVALID",
+ message: req.t("auth:register.DATE_OF_BIRTH_INVALID"),
+ },
+ });
+ }
- // higher is younger
- if (parsedDob > minimum) {
- throw FieldErrors({
- date_of_birth: {
- code: "DATE_OF_BIRTH_UNDERAGE",
- message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", {
- years: register.dateOfBirth.minimum,
- }),
- },
- });
- }
- }
+ // higher is younger
+ if (parsedDob > 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 (body.password) {
- const min = register.password.minLength ?? 8;
+ if (body.password) {
+ const min = register.password.minLength ?? 8;
- if (body.password.length < min) {
- throw FieldErrors({
- password: {
- code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
- message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }),
- },
- });
- }
- // the salt is saved in the password refer to bcrypt docs
- body.password = await bcrypt.hash(body.password, 12);
- } else if (register.password.required) {
- throw FieldErrors({
- password: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (body.password.length < min) {
+ throw FieldErrors({
+ password: {
+ code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
+ message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }),
+ },
+ });
+ }
+ // the salt is saved in the password refer to bcrypt docs
+ body.password = await bcrypt.hash(body.password, 12);
+ } else if (register.password.required) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) {
- // 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"),
- },
- });
- }
+ if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) {
+ // 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"),
+ },
+ });
+ }
- if (
- !regTokenUsed &&
- limits.absoluteRate.register.enabled &&
- (await User.count({
- where: {
- created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)),
- },
- })) >= limits.absoluteRate.register.limit
- ) {
- console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`);
- throw FieldErrors({
- email: {
- code: "TOO_MANY_REGISTRATIONS",
- message: req.t("auth:register.TOO_MANY_REGISTRATIONS"),
- },
- });
- }
+ if (
+ !regTokenUsed &&
+ limits.absoluteRate.register.enabled &&
+ (await User.count({
+ where: {
+ created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)),
+ },
+ })) >= limits.absoluteRate.register.limit
+ ) {
+ console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`);
+ throw FieldErrors({
+ email: {
+ code: "TOO_MANY_REGISTRATIONS",
+ message: req.t("auth:register.TOO_MANY_REGISTRATIONS"),
+ },
+ });
+ }
- const { maxUsername } = Config.get().limits.user;
- if (body.username.length > maxUsername) {
- throw FieldErrors({
- username: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 2 and ${maxUsername} in length.`,
- },
- });
- }
+ const { maxUsername } = Config.get().limits.user;
+ if (body.username.length > maxUsername) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 2 and ${maxUsername} in length.`,
+ },
+ });
+ }
- const user = await User.register({ ...body, req });
+ const user = await User.register({ ...body, req });
- if (body.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, body.invite);
- }
+ if (body.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, body.invite);
+ }
- return res.json({ token: await generateToken(user.id) });
- },
+ return res.json({ token: await generateToken(user.id) });
+ },
);
export default router;
diff --git a/src/api/routes/auth/reset.ts b/src/api/routes/auth/reset.ts
index 69f97d28a..77e93e840 100644
--- a/src/api/routes/auth/reset.ts
+++ b/src/api/routes/auth/reset.ts
@@ -26,54 +26,54 @@ const router = Router({ mergeParams: true });
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post(
- "/",
- route({
- requestBody: "PasswordResetSchema",
- responses: {
- 200: {
- body: "TokenOnlyResponse",
- },
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { password, token } = req.body as PasswordResetSchema;
+ "/",
+ route({
+ requestBody: "PasswordResetSchema",
+ responses: {
+ 200: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { password, token } = req.body as PasswordResetSchema;
- let user;
- try {
- const userTokenData = await checkToken(token, {
- select: ["email"],
- fingerprint: req.fingerprint,
- ipAddress: req.ip,
- });
- user = userTokenData.user;
- } catch {
- throw FieldErrors({
- password: {
- message: req.t("auth:password_reset.INVALID_TOKEN"),
- code: "INVALID_TOKEN",
- },
- });
- }
+ let user;
+ try {
+ const userTokenData = await checkToken(token, {
+ select: ["email"],
+ fingerprint: req.fingerprint,
+ ipAddress: req.ip,
+ });
+ user = userTokenData.user;
+ } catch {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:password_reset.INVALID_TOKEN"),
+ code: "INVALID_TOKEN",
+ },
+ });
+ }
- // the salt is saved in the password refer to bcrypt docs
- const hash = await bcrypt.hash(password, 12);
+ // the salt is saved in the password refer to bcrypt docs
+ const hash = await bcrypt.hash(password, 12);
- const data = {
- data: {
- hash,
- valid_tokens_since: new Date(),
- },
- };
- await User.update({ id: user.id }, data);
+ const data = {
+ data: {
+ hash,
+ valid_tokens_since: new Date(),
+ },
+ };
+ await User.update({ id: user.id }, data);
- // come on, the user has to have an email to reset their password in the first place
- await Email.sendPasswordChanged(user, user.email!);
+ // come on, the user has to have an email to reset their password in the first place
+ await Email.sendPasswordChanged(user, user.email!);
- res.json({ token: await generateToken(user.id) });
- },
+ res.json({ token: await generateToken(user.id) });
+ },
);
export default router;
diff --git a/src/api/routes/auth/sessions.ts b/src/api/routes/auth/sessions.ts
index 6c721f2e5..3a9095f14 100644
--- a/src/api/routes/auth/sessions.ts
+++ b/src/api/routes/auth/sessions.ts
@@ -23,54 +23,54 @@ import { SessionsLogoutSchema } from "../../../schemas/api/users/SessionsSchemas
import { In } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GetSessionsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { extended = false } = req.query;
- const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[];
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GetSessionsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { extended = false } = req.query;
+ const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[];
- res.json({
- user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())),
- });
- },
+ res.json({
+ user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())),
+ });
+ },
);
router.post(
- "/logout",
- route({
- requestBody: "SessionsLogoutSchema",
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as SessionsLogoutSchema;
+ "/logout",
+ route({
+ requestBody: "SessionsLogoutSchema",
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as SessionsLogoutSchema;
- let sessions: Session[] = [];
- if ("session_ids" in body) {
- sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[];
- }
+ let sessions: Session[] = [];
+ if ("session_ids" in body) {
+ sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[];
+ }
- if ("session_id_hashes" in body) {
- const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[];
- const hashSet = new Set(body.session_id_hashes);
- const matchingSessions = allSessions.filter((session) => {
- const hash = createHash("sha256").update(session.session_id).digest("hex");
- return hashSet.has(hash);
- });
- sessions.push(...matchingSessions);
- }
+ if ("session_id_hashes" in body) {
+ const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[];
+ const hashSet = new Set(body.session_id_hashes);
+ const matchingSessions = allSessions.filter((session) => {
+ const hash = createHash("sha256").update(session.session_id).digest("hex");
+ return hashSet.has(hash);
+ });
+ sessions.push(...matchingSessions);
+ }
- for (const session of sessions) {
- await session.remove();
- }
- res.status(204).send();
- },
+ for (const session of sessions) {
+ await session.remove();
+ }
+ res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts
index 43fb23a02..7c8ca1876 100644
--- a/src/api/routes/auth/verify/index.ts
+++ b/src/api/routes/auth/verify/index.ts
@@ -22,79 +22,79 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
async function getToken(user: User) {
- const token = await generateToken(user.id);
+ 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
+ // 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
- return { token };
+ return { token };
}
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post(
- "/",
- route({
- requestBody: "VerifyEmailSchema",
- responses: {
- 200: {
- body: "TokenResponse",
- },
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { captcha_key, token } = req.body;
+ "/",
+ route({
+ requestBody: "VerifyEmailSchema",
+ responses: {
+ 200: {
+ body: "TokenResponse",
+ },
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { captcha_key, token } = req.body;
- const config = Config.get();
+ const config = Config.get();
- if (config.register.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
+ if (config.register.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const ip = req.ip;
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const ip = req.ip;
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- let user;
+ let user;
- try {
- const userTokenData = await checkToken(token, {
- fingerprint: req.fingerprint,
- ipAddress: req.ip,
- });
- user = userTokenData.user;
- } catch {
- throw FieldErrors({
- token: {
- message: req.t("auth:password_reset.INVALID_TOKEN"),
- code: "INVALID_TOKEN",
- },
- });
- }
+ try {
+ const userTokenData = await checkToken(token, {
+ fingerprint: req.fingerprint,
+ ipAddress: req.ip,
+ });
+ user = userTokenData.user;
+ } catch {
+ throw FieldErrors({
+ token: {
+ message: req.t("auth:password_reset.INVALID_TOKEN"),
+ code: "INVALID_TOKEN",
+ },
+ });
+ }
- if (user.verified) return res.json(await getToken(user));
+ if (user.verified) return res.json(await getToken(user));
- await User.update({ id: user.id }, { verified: true });
+ await User.update({ id: user.id }, { verified: true });
- return res.json(await getToken(user));
- },
+ return res.json(await getToken(user));
+ },
);
export default router;
diff --git a/src/api/routes/auth/verify/resend.ts b/src/api/routes/auth/verify/resend.ts
index 4ef1d5183..ce6672a7c 100644
--- a/src/api/routes/auth/verify/resend.ts
+++ b/src/api/routes/auth/verify/resend.ts
@@ -23,43 +23,43 @@ import { HTTPError } from "lambert-server";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- right: "RESEND_VERIFICATION_EMAIL",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 500: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["username", "email", "verified"],
- });
+ "/",
+ route({
+ right: "RESEND_VERIFICATION_EMAIL",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 500: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["username", "email", "verified"],
+ });
- if (!user.email) {
- // TODO: whats the proper error response for this?
- throw new HTTPError("User does not have an email address", 400);
- }
+ if (!user.email) {
+ // TODO: whats the proper error response for this?
+ throw new HTTPError("User does not have an email address", 400);
+ }
- if (user.verified) {
- throw new HTTPError("Email is already verified", 400);
- }
+ if (user.verified) {
+ throw new HTTPError("Email is already verified", 400);
+ }
- await Email.sendVerifyEmail(user, user.email)
- .then(() => {
- return res.sendStatus(204);
- })
- .catch((e) => {
- console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`);
- throw new HTTPError("Failed to send verification email", 500);
- });
- },
+ await Email.sendVerifyEmail(user, user.email)
+ .then(() => {
+ return res.sendStatus(204);
+ })
+ .catch((e) => {
+ console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`);
+ throw new HTTPError("Failed to send verification email", 500);
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/verify/view-backup-codes-challenge.ts b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
index 7c15795c8..d3d40d3e3 100644
--- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts
+++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
@@ -24,36 +24,36 @@ import { BackupCodesChallengeSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "BackupCodesChallengeSchema",
- responses: {
- 200: { body: "BackupCodesChallengeResponse" },
- 400: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { password } = req.body as BackupCodesChallengeSchema;
+ "/",
+ route({
+ requestBody: "BackupCodesChallengeSchema",
+ responses: {
+ 200: { body: "BackupCodesChallengeResponse" },
+ 400: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { password } = req.body as BackupCodesChallengeSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- if (!(await bcrypt.compare(password, user.data.hash || ""))) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- return res.json({
- nonce: "NoncePlaceholder",
- regenerate_nonce: "RegenNoncePlaceholder",
- });
- },
+ return res.json({
+ nonce: "NoncePlaceholder",
+ regenerate_nonce: "RegenNoncePlaceholder",
+ });
+ },
);
export default router;
diff --git a/src/api/routes/beaker.ts b/src/api/routes/beaker.ts
index 62df41e16..4eb2941a1 100644
--- a/src/api/routes/beaker.ts
+++ b/src/api/routes/beaker.ts
@@ -23,16 +23,16 @@ const router = Router({ mergeParams: true });
// screw off, telemetry requests
router.post(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
- },
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/attachments.ts b/src/api/routes/channels/#channel_id/attachments.ts
index 039bfa21d..30fd2c63f 100644
--- a/src/api/routes/channels/#channel_id/attachments.ts
+++ b/src/api/routes/channels/#channel_id/attachments.ts
@@ -25,109 +25,109 @@ import { UploadAttachmentRequestSchema, UploadAttachmentResponseSchema } from "@
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "UploadAttachmentRequestSchema",
- responses: {
- 200: {
- body: "UploadAttachmentResponseSchema",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const payload = req.body as UploadAttachmentRequestSchema;
- const { channel_id } = req.params;
+ "/",
+ route({
+ requestBody: "UploadAttachmentRequestSchema",
+ responses: {
+ 200: {
+ body: "UploadAttachmentResponseSchema",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const payload = req.body as UploadAttachmentRequestSchema;
+ const { channel_id } = req.params;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) {
- return res.status(403).json({
- code: 403,
- message: "Missing Permissions: ATTACH_FILES",
- });
- }
+ if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) {
+ return res.status(403).json({
+ code: 403,
+ message: "Missing Permissions: ATTACH_FILES",
+ });
+ }
- const cdnUrl = Config.get().cdn.endpointPublic;
- const batchId = `CLOUD_${user.id}_${randomString(128)}`;
+ const cdnUrl = Config.get().cdn.endpointPublic;
+ const batchId = `CLOUD_${user.id}_${randomString(128)}`;
- // validate IDs
- const seenIds: (string | undefined)[] = [];
- for (const file of payload.files) {
- if (seenIds.includes(file.id)) {
- return res.status(400).json({
- code: 400,
- message: `Duplicate attachment ID: ${file.id}`,
- });
- }
- seenIds.push(file.id);
- }
+ // validate IDs
+ const seenIds: (string | undefined)[] = [];
+ for (const file of payload.files) {
+ if (seenIds.includes(file.id)) {
+ return res.status(400).json({
+ code: 400,
+ message: `Duplicate attachment ID: ${file.id}`,
+ });
+ }
+ seenIds.push(file.id);
+ }
- const attachments = await Promise.all(
- payload.files.map(async (attachment) => {
- attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, "");
- const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`;
- const newAttachment = CloudAttachment.create({
- user: user,
- channel: channel,
- uploadFilename: uploadFilename,
- userAttachmentId: attachment.id ?? "0",
- userFilename: attachment.filename,
- userFileSize: attachment.file_size,
- userIsClip: attachment.is_clip,
- userOriginalContentType: attachment.original_content_type,
- });
- await newAttachment.save();
- return newAttachment;
- }),
- );
+ const attachments = await Promise.all(
+ payload.files.map(async (attachment) => {
+ attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, "");
+ const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`;
+ const newAttachment = CloudAttachment.create({
+ user: user,
+ channel: channel,
+ uploadFilename: uploadFilename,
+ userAttachmentId: attachment.id ?? "0",
+ userFilename: attachment.filename,
+ userFileSize: attachment.file_size,
+ userIsClip: attachment.is_clip,
+ userOriginalContentType: attachment.original_content_type,
+ });
+ await newAttachment.save();
+ return newAttachment;
+ }),
+ );
- res.send({
- attachments: attachments.map((a) => {
- return {
- id: a.userAttachmentId,
- upload_filename: a.uploadFilename,
- upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`,
- original_content_type: a.userOriginalContentType,
- };
- }),
- } as UploadAttachmentResponseSchema);
- },
+ res.send({
+ attachments: attachments.map((a) => {
+ return {
+ id: a.userAttachmentId,
+ upload_filename: a.uploadFilename,
+ upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`,
+ original_content_type: a.userOriginalContentType,
+ };
+ }),
+ } as UploadAttachmentResponseSchema);
+ },
);
router.delete("/:cloud_attachment_url", async (req: Request, res: Response) => {
- const { channel_id, cloud_attachment_url } = req.params;
+ const { channel_id, cloud_attachment_url } = req.params;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } });
- if (att.userId !== user.id) {
- return res.status(403).json({
- code: 403,
- message: "You do not own this attachment.",
- });
- }
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } });
+ if (att.userId !== user.id) {
+ return res.status(403).json({
+ code: 403,
+ message: "You do not own this attachment.",
+ });
+ }
- if (att.channelId !== channel.id) {
- return res.status(400).json({
- code: 400,
- message: "Attachment does not belong to this channel.",
- });
- }
+ if (att.channelId !== channel.id) {
+ return res.status(400).json({
+ code: 400,
+ message: "Attachment does not belong to this channel.",
+ });
+ }
- const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, {
- headers: {
- signature: Config.get().security.requestSignature,
- },
- method: "DELETE",
- });
+ const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, {
+ headers: {
+ signature: Config.get().security.requestSignature,
+ },
+ method: "DELETE",
+ });
- await att.remove();
- return res.status(response.status).send(response.body);
+ await att.remove();
+ return res.status(response.status).send(response.body);
});
export default router;
diff --git a/src/api/routes/channels/#channel_id/directory-entries.ts b/src/api/routes/channels/#channel_id/directory-entries.ts
index c07b1f751..da950502a 100644
--- a/src/api/routes/channels/#channel_id/directory-entries.ts
+++ b/src/api/routes/channels/#channel_id/directory-entries.ts
@@ -22,20 +22,20 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "HubDirectoryEntriesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json([] as HubDirectoryEntriesResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "HubDirectoryEntriesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json([] as HubDirectoryEntriesResponse);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/greet.ts b/src/api/routes/channels/#channel_id/greet.ts
index b6ce1ee51..f78f26ab1 100644
--- a/src/api/routes/channels/#channel_id/greet.ts
+++ b/src/api/routes/channels/#channel_id/greet.ts
@@ -25,83 +25,83 @@ import { GreetRequestSchema, MessageType } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "GreetRequestSchema",
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "Message",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const payload = req.body as GreetRequestSchema;
- const { channel_id } = req.params;
+ "/",
+ route({
+ requestBody: "GreetRequestSchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const payload = req.body as GreetRequestSchema;
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- const targetMessage = await Message.findOneOrFail({
- where: {
- id: payload.message_reference?.message_id,
- channel_id: payload.message_reference?.channel_id,
- guild_id: payload.message_reference?.guild_id,
- },
- });
+ const targetMessage = await Message.findOneOrFail({
+ where: {
+ id: payload.message_reference?.message_id,
+ channel_id: payload.message_reference?.channel_id,
+ guild_id: payload.message_reference?.guild_id,
+ },
+ });
- if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN)
- return res.status(400).json({
- code: 400, // TODO: what's the actual error code?
- message: "Cannot send greet message referencing this message.",
- });
+ if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN)
+ return res.status(400).json({
+ code: 400, // TODO: what's the actual error code?
+ message: "Cannot send greet message referencing this message.",
+ });
- if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) {
- return res.status(403).json({
- code: 403,
- message: "Missing Permissions: SEND_MESSAGES",
- });
- }
+ if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) {
+ return res.status(403).json({
+ code: 403,
+ message: "Missing Permissions: SEND_MESSAGES",
+ });
+ }
- const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick
- if (specCompliant && payload.sticker_ids.length != 1)
- return res.status(400).json({
- code: 400,
- message: "Must include exactly one sticker.",
- });
+ const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick
+ if (specCompliant && payload.sticker_ids.length != 1)
+ return res.status(400).json({
+ code: 400,
+ message: "Must include exactly one sticker.",
+ });
- const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } });
+ const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } });
- const randomSticker = stickers[Math.floor(Math.random() * stickers.length)];
+ const randomSticker = stickers[Math.floor(Math.random() * stickers.length)];
- const message = Message.create({
- channel_id: channel_id,
- author_id: req.user_id,
- type: MessageType.REPLY,
- message_reference: { ...payload.message_reference, type: 0 },
- referenced_message: targetMessage,
- sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [],
- });
+ const message = Message.create({
+ channel_id: channel_id,
+ author_id: req.user_id,
+ type: MessageType.REPLY,
+ message_reference: { ...payload.message_reference, type: 0 },
+ referenced_message: targetMessage,
+ sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [],
+ });
- channel.last_message_id = message.id;
+ channel.last_message_id = message.id;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- data: message,
- channel_id,
- } as MessageCreateEvent),
- channel.save(),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ data: message,
+ channel_id,
+ } as MessageCreateEvent),
+ channel.save(),
+ ]);
- res.send(channel);
- },
+ res.send(channel);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 97857da6a..37909a94d 100644
--- a/src/api/routes/channels/#channel_id/index.ts
+++ b/src/api/routes/channels/#channel_id/index.ts
@@ -26,132 +26,132 @@ const router: Router = Router({ mergeParams: true });
// TODO: Get channel
router.get(
- "/",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 200: {
- body: "Channel",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 200: {
+ body: "Channel",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) return res.send(channel);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) return res.send(channel);
- channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
- return res.send(channel);
- },
+ channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
+ return res.send(channel);
+ },
);
router.delete(
- "/",
- route({
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "Channel",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Channel",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
+ 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;
- 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);
- } else {
- if (channel.type == ChannelType.GUILD_CATEGORY) {
- const channels = await Channel.find({
- where: { parent_id: channel_id },
- });
- for await (const c of channels) {
- c.parent_id = null;
+ 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),
+ ]);
+ } else if (channel.type === ChannelType.GROUP_DM) {
+ await Channel.removeRecipientFromChannel(channel, req.user_id);
+ } else {
+ if (channel.type == ChannelType.GUILD_CATEGORY) {
+ const channels = await Channel.find({
+ where: { parent_id: channel_id },
+ });
+ for await (const c of channels) {
+ c.parent_id = null;
- await Promise.all([
- c.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- data: c,
- channel_id: c.id,
- } as ChannelUpdateEvent),
- ]);
- }
- }
+ await Promise.all([
+ c.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: c,
+ channel_id: c.id,
+ } as ChannelUpdateEvent),
+ ]);
+ }
+ }
- await Promise.all([
- Channel.deleteChannel(channel),
- emitEvent({
- event: "CHANNEL_DELETE",
- data: channel,
- channel_id,
- } as ChannelDeleteEvent),
- ]);
- }
+ await Promise.all([
+ Channel.deleteChannel(channel),
+ emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel,
+ channel_id,
+ } as ChannelDeleteEvent),
+ ]);
+ }
- res.send(channel);
- },
+ res.send(channel);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ChannelModifySchema",
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "Channel",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const payload = req.body as ChannelModifySchema;
- const { channel_id } = req.params;
- if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
+ "/",
+ route({
+ requestBody: "ChannelModifySchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Channel",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const 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({
- where: { id: channel_id },
- });
- channel.assign(payload);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ channel.assign(payload);
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id,
- } as ChannelUpdateEvent),
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id,
+ } as ChannelUpdateEvent),
+ ]);
- res.send(channel);
- },
+ res.send(channel);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts
index 298f42ed7..808f0f8c7 100644
--- a/src/api/routes/channels/#channel_id/invites.ts
+++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -25,96 +25,96 @@ import { InviteCreateSchema, isTextChannel } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "InviteCreateSchema",
- permission: "CREATE_INSTANT_INVITE",
- right: "CREATE_INVITES",
- responses: {
- 201: {
- body: "Invite",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req;
- const body = req.body as InviteCreateSchema;
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- select: ["id", "name", "type", "guild_id"],
- });
- isTextChannel(channel.type);
+ "/",
+ route({
+ requestBody: "InviteCreateSchema",
+ permission: "CREATE_INSTANT_INVITE",
+ right: "CREATE_INVITES",
+ responses: {
+ 201: {
+ body: "Invite",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req;
+ const body = req.body as InviteCreateSchema;
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ select: ["id", "name", "type", "guild_id"],
+ });
+ isTextChannel(channel.type);
- if (!channel.guild_id) {
- throw new HTTPError("This channel doesn't exist", 404);
- }
- const { guild_id } = channel;
+ if (!channel.guild_id) {
+ throw new HTTPError("This channel doesn't exist", 404);
+ }
+ const { guild_id } = channel;
- const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now());
+ const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now());
- const invite = await Invite.create({
- code: randomString(),
- temporary: body.temporary || true,
- uses: 0,
- max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0,
- max_age: body.max_age ? Math.max(0, body.max_age) : 0,
- expires_at,
- created_at: new Date(),
- guild_id,
- channel_id: channel_id,
- inviter_id: user_id,
- flags: body.flags ?? 0,
- }).save();
+ const invite = await Invite.create({
+ code: randomString(),
+ temporary: body.temporary || true,
+ uses: 0,
+ max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0,
+ max_age: body.max_age ? Math.max(0, body.max_age) : 0,
+ expires_at,
+ created_at: new Date(),
+ guild_id,
+ channel_id: channel_id,
+ inviter_id: user_id,
+ flags: body.flags ?? 0,
+ }).save();
- const data = invite.toJSON();
- data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser();
- data.guild = await Guild.findOne({ where: { id: guild_id } });
- data.channel = channel;
+ const data = invite.toJSON();
+ data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser();
+ data.guild = await Guild.findOne({ where: { id: guild_id } });
+ data.channel = channel;
- await emitEvent({
- event: "INVITE_CREATE",
- data,
- guild_id,
- } as InviteCreateEvent);
+ await emitEvent({
+ event: "INVITE_CREATE",
+ data,
+ guild_id,
+ } as InviteCreateEvent);
- res.status(201).send(data);
- },
+ res.status(201).send(data);
+ },
);
router.get(
- "/",
- route({
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "APIInviteArray",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ "/",
+ route({
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "APIInviteArray",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- if (!channel.guild_id) {
- throw new HTTPError("This channel doesn't exist", 404);
- }
- const { guild_id } = channel;
+ if (!channel.guild_id) {
+ throw new HTTPError("This channel doesn't exist", 404);
+ }
+ const { guild_id } = channel;
- const invites = await Invite.find({
- where: { guild_id, channel_id },
- relations: PublicInviteRelation,
- });
+ const invites = await Invite.find({
+ where: { guild_id, channel_id },
+ relations: PublicInviteRelation,
+ });
- res.status(200).send(invites);
- },
+ res.status(200).send(invites);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index c7c96fea7..7b9f0397f 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -27,42 +27,42 @@ const router = Router({ mergeParams: true });
// TODO: advance-only notification cursor
router.post(
- "/",
- route({
- requestBody: "MessageAcknowledgeSchema",
- responses: {
- 200: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/",
+ route({
+ requestBody: "MessageAcknowledgeSchema",
+ responses: {
+ 200: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const permission = await getPermission(req.user_id, undefined, channel_id);
- permission.hasThrow("VIEW_CHANNEL");
+ const permission = await getPermission(req.user_id, undefined, channel_id);
+ permission.hasThrow("VIEW_CHANNEL");
- let read_state = await ReadState.findOne({
- where: { user_id: req.user_id, channel_id },
- });
- if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
- read_state.last_message_id = message_id;
- //It's a little more complicated but this'll do :P
- read_state.mention_count = 0;
+ let read_state = await ReadState.findOne({
+ where: { user_id: req.user_id, channel_id },
+ });
+ if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
+ read_state.last_message_id = message_id;
+ //It's a little more complicated but this'll do :P
+ read_state.mention_count = 0;
- await read_state.save();
+ await read_state.save();
- await emitEvent({
- event: "MESSAGE_ACK",
- user_id: req.user_id,
- data: {
- channel_id,
- message_id,
- version: 3763,
- },
- } as MessageAckEvent);
+ await emitEvent({
+ event: "MESSAGE_ACK",
+ user_id: req.user_id,
+ data: {
+ channel_id,
+ message_id,
+ version: 3763,
+ },
+ } as MessageAckEvent);
- res.json({ token: null });
- },
+ res.json({ token: null });
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index 6d89c504a..2aad157df 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
@@ -22,43 +22,43 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- permission: "MANAGE_MESSAGES",
- responses: {
- 200: {
- body: "Message",
- },
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- res.json({
- id: "",
- type: 0,
- content: "",
- channel_id: "",
- author: {
- id: "",
- username: "",
- avatar: "",
- discriminator: "",
- public_flags: 64,
- },
- attachments: [],
- embeds: [],
- mentions: [],
- mention_roles: [],
- pinned: false,
- mention_everyone: false,
- tts: false,
- timestamp: "",
- edited_timestamp: null,
- flags: 1,
- components: [],
- poll: {},
- }).status(200);
- },
+ "/",
+ route({
+ permission: "MANAGE_MESSAGES",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.json({
+ id: "",
+ type: 0,
+ content: "",
+ channel_id: "",
+ author: {
+ id: "",
+ username: "",
+ avatar: "",
+ discriminator: "",
+ public_flags: 64,
+ },
+ attachments: [],
+ embeds: [],
+ mentions: [],
+ mention_roles: [],
+ pinned: false,
+ mention_everyone: false,
+ tts: false,
+ timestamp: "",
+ edited_timestamp: null,
+ flags: 1,
+ components: [],
+ poll: {},
+ }).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index cdba9b168..64f29c931 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -17,19 +17,19 @@
*/
import {
- Attachment,
- Channel,
- Message,
- MessageCreateEvent,
- MessageDeleteEvent,
- MessageUpdateEvent,
- Snowflake,
- SpacebarApiErrors,
- emitEvent,
- getPermission,
- getRights,
- uploadFile,
- NewUrlUserSignatureData,
+ Attachment,
+ Channel,
+ Message,
+ MessageCreateEvent,
+ MessageDeleteEvent,
+ MessageUpdateEvent,
+ Snowflake,
+ SpacebarApiErrors,
+ emitEvent,
+ getPermission,
+ getRights,
+ uploadFile,
+ NewUrlUserSignatureData,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@@ -41,285 +41,285 @@ const router = Router({ mergeParams: true });
// TODO: message content/embed string length limit
const messageUpload = multer({
- limits: {
- fileSize: 1024 * 1024 * 100,
- fields: 10,
- files: 1,
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: 1024 * 1024 * 100,
+ fields: 10,
+ files: 1,
+ },
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
router.patch(
- "/",
- route({
- requestBody: "MessageEditSchema",
- permission: "SEND_MESSAGES",
- right: "SEND_MESSAGES",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- let body = req.body as MessageEditSchema;
+ "/",
+ route({
+ requestBody: "MessageEditSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ let body = req.body as MessageEditSchema;
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- relations: ["attachments"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ relations: ["attachments"],
+ });
- const permissions = await getPermission(req.user_id, undefined, channel_id);
+ const permissions = await getPermission(req.user_id, undefined, channel_id);
- const rights = await getRights(req.user_id);
+ const rights = await getRights(req.user_id);
- if (req.user_id !== message.author_id) {
- if (!rights.has("MANAGE_MESSAGES")) {
- permissions.hasThrow("MANAGE_MESSAGES");
- body = { flags: body.flags };
- // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
- }
- } else rights.hasThrow("SELF_EDIT_MESSAGES");
+ if (req.user_id !== message.author_id) {
+ if (!rights.has("MANAGE_MESSAGES")) {
+ permissions.hasThrow("MANAGE_MESSAGES");
+ body = { flags: body.flags };
+ // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
+ }
+ } else rights.hasThrow("SELF_EDIT_MESSAGES");
- // no longer necessary, somehow resolved by updating the type of `attachments`...?
- // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema
- const new_message = await handleMessage({
- ...message,
- // TODO: should message_reference be overridable?
- message_reference: message.message_reference,
- ...body,
- author_id: message.author_id,
- channel_id,
- id: message_id,
- edited_timestamp: new Date(),
- });
+ // no longer necessary, somehow resolved by updating the type of `attachments`...?
+ // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema
+ const new_message = await handleMessage({
+ ...message,
+ // TODO: should message_reference be overridable?
+ message_reference: message.message_reference,
+ ...body,
+ author_id: message.author_id,
+ channel_id,
+ id: message_id,
+ edited_timestamp: new Date(),
+ });
- await Promise.all([
- new_message.save(),
- await emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: {
- ...new_message.toJSON(),
- nonce: undefined,
- member: new_message.member?.toPublicMember(),
- },
- } as MessageUpdateEvent),
- ]);
+ await Promise.all([
+ new_message.save(),
+ await emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: {
+ ...new_message.toJSON(),
+ nonce: undefined,
+ member: new_message.member?.toPublicMember(),
+ },
+ } as MessageUpdateEvent),
+ ]);
- postHandleMessage(new_message);
+ postHandleMessage(new_message);
- // TODO: a DTO?
- return res.json({
- ...new_message.toJSON(),
- id: new_message.id,
- type: new_message.type,
- channel_id: new_message.channel_id,
- member: new_message.member?.toPublicMember(),
- author: new_message.author?.toPublicUser(),
- attachments: new_message.attachments,
- embeds: new_message.embeds,
- mentions: new_message.embeds,
- mention_roles: new_message.mention_roles,
- mention_everyone: new_message.mention_everyone,
- pinned: new_message.pinned,
- timestamp: new_message.timestamp,
- edited_timestamp: new_message.edited_timestamp,
+ // TODO: a DTO?
+ return res.json({
+ ...new_message.toJSON(),
+ id: new_message.id,
+ type: new_message.type,
+ channel_id: new_message.channel_id,
+ member: new_message.member?.toPublicMember(),
+ author: new_message.author?.toPublicUser(),
+ attachments: new_message.attachments,
+ embeds: new_message.embeds,
+ mentions: new_message.embeds,
+ mention_roles: new_message.mention_roles,
+ mention_everyone: new_message.mention_everyone,
+ pinned: new_message.pinned,
+ timestamp: new_message.timestamp,
+ edited_timestamp: new_message.edited_timestamp,
- // these are not in the Discord.com response
- mention_channels: new_message.mention_channels,
- });
- },
+ // these are not in the Discord.com response
+ mention_channels: new_message.mention_channels,
+ });
+ },
);
// Backfill message with specific timestamp
router.put(
- "/",
- messageUpload.single("file"),
- async (req, res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
+ "/",
+ messageUpload.single("file"),
+ async (req, res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- next();
- },
- route({
- requestBody: "MessageCreateSchema",
- permission: "SEND_MESSAGES",
- right: "SEND_BACKDATED_EVENTS",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
- const body = req.body as MessageCreateSchema;
- const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
+ next();
+ },
+ route({
+ requestBody: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_BACKDATED_EVENTS",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
+ const body = req.body as MessageCreateSchema;
+ const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
- const rights = await getRights(req.user_id);
- rights.hasThrow("SEND_MESSAGES");
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("SEND_MESSAGES");
- // regex to check if message contains anything other than numerals ( also no decimals )
- if (!message_id.match(/^\+?\d+$/)) {
- throw new HTTPError("Message IDs must be positive integers", 400);
- }
+ // regex to check if message contains anything other than numerals ( also no decimals )
+ if (!message_id.match(/^\+?\d+$/)) {
+ throw new HTTPError("Message IDs must be positive integers", 400);
+ }
- const snowflake = Snowflake.deconstruct(message_id);
- if (Date.now() < snowflake.timestamp) {
- // message is in the future
- throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
- }
+ const snowflake = Snowflake.deconstruct(message_id);
+ if (Date.now() < snowflake.timestamp) {
+ // message is in the future
+ throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
+ }
- const exists = await Message.findOne({
- where: { id: message_id, channel_id: channel_id },
- });
- if (exists) {
- throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL;
- }
+ const exists = await Message.findOne({
+ where: { id: message_id, channel_id: channel_id },
+ });
+ if (exists) {
+ throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL;
+ }
- if (req.file) {
- try {
- const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- } catch (error) {
- return res.status(400).json(error);
- }
- }
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients", "recipients.user"],
- });
+ if (req.file) {
+ try {
+ const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
+ attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ } catch (error) {
+ return res.status(400).json(error);
+ }
+ }
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
- const embeds = body.embeds || [];
- if (body.embed) embeds.push(body.embed);
- const message = await handleMessage({
- ...body,
- type: 0,
- pinned: false,
- author_id: req.user_id,
- id: message_id,
- embeds,
- channel_id,
- attachments,
- edited_timestamp: undefined,
- timestamp: new Date(snowflake.timestamp),
- });
+ const embeds = body.embeds || [];
+ if (body.embed) embeds.push(body.embed);
+ const message = await handleMessage({
+ ...body,
+ type: 0,
+ pinned: false,
+ author_id: req.user_id,
+ id: message_id,
+ embeds,
+ channel_id,
+ attachments,
+ edited_timestamp: undefined,
+ timestamp: new Date(snowflake.timestamp),
+ });
- //Fix for the client bug
- delete message.member;
+ //Fix for the client bug
+ delete message.member;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: channel_id,
- data: message,
- } as MessageCreateEvent),
- channel.save(),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ channel.save(),
+ ]);
- // no await as it shouldnt block the message send function and silently catch error
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- return res.json(
- message.withSignedAttachments(
- new NewUrlUserSignatureData({
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- ),
- );
- },
+ return res.json(
+ message.withSignedAttachments(
+ new NewUrlUserSignatureData({
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ ),
+ );
+ },
);
router.get(
- "/",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+ "/",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- relations: ["attachments"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ relations: ["attachments"],
+ });
- const permissions = await getPermission(req.user_id, undefined, channel_id);
+ const permissions = await getPermission(req.user_id, undefined, channel_id);
- if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
+ if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
- return res.json(message);
- },
+ return res.json(message);
+ },
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ });
- const rights = await getRights(req.user_id);
+ const rights = await getRights(req.user_id);
- if (message.author_id !== req.user_id) {
- if (!rights.has("MANAGE_MESSAGES")) {
- const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
- permission.hasThrow("MANAGE_MESSAGES");
- }
- } else rights.hasThrow("SELF_DELETE_MESSAGES");
+ if (message.author_id !== req.user_id) {
+ if (!rights.has("MANAGE_MESSAGES")) {
+ const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
+ permission.hasThrow("MANAGE_MESSAGES");
+ }
+ } else rights.hasThrow("SELF_DELETE_MESSAGES");
- await Message.delete({ id: message_id });
+ await Message.delete({ id: message_id });
- await emitEvent({
- event: "MESSAGE_DELETE",
- channel_id,
- data: {
- id: message_id,
- channel_id,
- guild_id: channel.guild_id,
- },
- } as MessageDeleteEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE",
+ channel_id,
+ data: {
+ id: message_id,
+ channel_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageDeleteEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index 63c41cd7e..b21dbd349 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -18,18 +18,18 @@
import { route } from "@spacebar/api";
import {
- Channel,
- emitEvent,
- Emoji,
- getPermission,
- Member,
- Message,
- MessageReactionAddEvent,
- MessageReactionRemoveAllEvent,
- MessageReactionRemoveEmojiEvent,
- MessageReactionRemoveEvent,
- User,
- arrayRemove,
+ Channel,
+ emitEvent,
+ Emoji,
+ getPermission,
+ Member,
+ Message,
+ MessageReactionAddEvent,
+ MessageReactionRemoveAllEvent,
+ MessageReactionRemoveEmojiEvent,
+ MessageReactionRemoveEvent,
+ User,
+ arrayRemove,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@@ -40,326 +40,326 @@ const router = Router({ mergeParams: true });
// TODO: check if emoji is really an unicode emoji or a properly encoded external emoji
function getEmoji(emoji: string): PartialEmoji {
- emoji = decodeURIComponent(emoji);
- const parts = emoji.includes(":") && emoji.split(":");
- if (parts)
- return {
- name: parts[0],
- id: parts[1],
- };
+ emoji = decodeURIComponent(emoji);
+ const parts = emoji.includes(":") && emoji.split(":");
+ if (parts)
+ return {
+ name: parts[0],
+ id: parts[1],
+ };
- return {
- id: undefined,
- name: emoji,
- };
+ return {
+ id: undefined,
+ name: emoji,
+ };
}
router.delete(
- "/",
- route({
- permission: "MANAGE_MESSAGES",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_MESSAGES",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- await Message.update({ id: message_id, channel_id }, { reactions: [] });
+ await Message.update({ id: message_id, channel_id }, { reactions: [] });
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE_ALL",
- channel_id,
- data: {
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- },
- } as MessageReactionRemoveAllEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_ALL",
+ channel_id,
+ data: {
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageReactionRemoveAllEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:emoji",
- route({
- permission: "MANAGE_MESSAGES",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ "/:emoji",
+ route({
+ permission: "MANAGE_MESSAGES",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ const emoji = getEmoji(req.params.emoji);
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added) throw new HTTPError("Reaction not found", 404);
- arrayRemove(message.reactions, already_added);
+ const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
+ if (!already_added) throw new HTTPError("Reaction not found", 404);
+ arrayRemove(message.reactions, already_added);
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_REACTION_REMOVE_EMOJI",
- channel_id,
- data: {
- channel_id,
- message_id,
- guild_id: message.guild_id,
- emoji,
- },
- } as MessageReactionRemoveEmojiEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_EMOJI",
+ channel_id,
+ data: {
+ channel_id,
+ message_id,
+ guild_id: message.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEmojiEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.get(
- "/:emoji",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 200: {
- body: "PublicUser",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ "/:emoji",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 200: {
+ body: "PublicUser",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ const emoji = getEmoji(req.params.emoji);
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
- 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 message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+ 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 users = (
- await User.find({
- where: {
- id: In(reaction.user_ids),
- },
- select: PublicUserProjection,
- })
- ).map((user) => user.toPublicUser());
+ const users = (
+ await User.find({
+ where: {
+ id: In(reaction.user_ids),
+ },
+ select: PublicUserProjection,
+ })
+ ).map((user) => user.toPublicUser());
- res.json(users);
- },
+ res.json(users);
+ },
);
router.put(
- "/:emoji/:user_id",
- route({
- permission: "READ_MESSAGE_HISTORY",
- right: "SELF_ADD_REACTIONS",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- 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);
+ "/:emoji/:user_id",
+ route({
+ permission: "READ_MESSAGE_HISTORY",
+ right: "SELF_ADD_REACTIONS",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ 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);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { 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 channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+ const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
+ if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
- if (emoji.id) {
- const external_emoji = await Emoji.findOneOrFail({
- where: { id: emoji.id },
- });
- if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS");
- emoji.animated = external_emoji.animated;
- emoji.name = external_emoji.name;
- }
+ if (emoji.id) {
+ const external_emoji = await Emoji.findOneOrFail({
+ where: { id: emoji.id },
+ });
+ if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS");
+ emoji.animated = external_emoji.animated;
+ emoji.name = external_emoji.name;
+ }
- if (already_added) {
- if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
- already_added.count++;
- already_added.user_ids.push(req.user_id);
- } else
- message.reactions.push({
- count: 1,
- emoji,
- user_ids: [req.user_id],
- });
+ if (already_added) {
+ if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
+ already_added.count++;
+ already_added.user_ids.push(req.user_id);
+ } else
+ message.reactions.push({
+ count: 1,
+ emoji,
+ user_ids: [req.user_id],
+ });
- await message.save();
+ await message.save();
- const member =
- channel.guild_id &&
- (
- await Member.findOneOrFail({
- where: { id: req.user_id },
- select: PublicMemberProjection,
- })
- ).toPublicMember();
+ const member =
+ channel.guild_id &&
+ (
+ await Member.findOneOrFail({
+ where: { id: req.user_id },
+ select: PublicMemberProjection,
+ })
+ ).toPublicMember();
- await emitEvent({
- event: "MESSAGE_REACTION_ADD",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- member,
- },
- } as MessageReactionAddEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_ADD",
+ channel_id,
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ member,
+ },
+ } as MessageReactionAddEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:emoji/:user_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- let { user_id } = req.params;
- const { message_id, channel_id } = req.params;
+ "/:emoji/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ let { user_id } = req.params;
+ const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
- if (user_id === "@me") user_id = req.user_id;
- else {
- const permissions = await getPermission(req.user_id, undefined, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- }
+ if (user_id === "@me") user_id = req.user_id;
+ 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);
+ 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);
- already_added.count--;
+ already_added.count--;
- if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
- else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
+ if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
+ else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
- await message.save();
+ await message.save();
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- },
- } as MessageReactionRemoveEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE",
+ channel_id,
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:emoji/:burst/:user_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- let { user_id } = req.params;
- const { message_id, channel_id } = req.params;
+ "/:emoji/:burst/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ let { user_id } = req.params;
+ const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
- if (user_id === "@me") user_id = req.user_id;
- else {
- const permissions = await getPermission(req.user_id, undefined, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- }
+ if (user_id === "@me") user_id = req.user_id;
+ 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);
+ 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);
- already_added.count--;
+ already_added.count--;
- if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
- else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
+ if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
+ else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
- await message.save();
+ await message.save();
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- },
- } as MessageReactionRemoveEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE",
+ channel_id,
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 54801a48b..f4645abdc 100644
--- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -29,48 +29,48 @@ export default router;
// should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
router.post(
- "/",
- route({
- requestBody: "BulkDeleteSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
+ "/",
+ route({
+ requestBody: "BulkDeleteSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
- const rights = await getRights(req.user_id);
- rights.hasThrow("SELF_DELETE_MESSAGES");
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("SELF_DELETE_MESSAGES");
- const superuser = rights.has("MANAGE_MESSAGES");
- const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
+ const superuser = rights.has("MANAGE_MESSAGES");
+ const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
- const { maxBulkDelete } = Config.get().limits.message;
+ const { maxBulkDelete } = Config.get().limits.message;
- const { messages } = req.body as { messages: string[] };
- if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
- if (!superuser) {
- permission.hasThrow("MANAGE_MESSAGES");
- if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
- }
+ const { messages } = req.body as { messages: string[] };
+ if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
+ if (!superuser) {
+ permission.hasThrow("MANAGE_MESSAGES");
+ if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
+ }
- await Message.delete(messages);
+ await Message.delete(messages);
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: { ids: messages, channel_id, guild_id: channel.guild_id },
- } as MessageDeleteBulkEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: { ids: messages, channel_id, guild_id: channel.guild_id },
+ } as MessageDeleteBulkEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 2ce37748f..272fa8ac8 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -18,29 +18,29 @@
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
import {
- Attachment,
- AutomodRule,
- AutomodTriggerTypes,
- Channel,
- Config,
- DiscordApiErrors,
- DmChannelDTO,
- emitEvent,
- FieldErrors,
- getPermission,
- getUrlSignature,
- Member,
- Message,
- MessageCreateEvent,
- NewUrlSignatureData,
- NewUrlUserSignatureData,
- ReadState,
- Relationship,
- Rights,
- Snowflake,
- stringGlobToRegexp,
- uploadFile,
- User,
+ Attachment,
+ AutomodRule,
+ AutomodTriggerTypes,
+ Channel,
+ Config,
+ DiscordApiErrors,
+ DmChannelDTO,
+ emitEvent,
+ FieldErrors,
+ getPermission,
+ getUrlSignature,
+ Member,
+ Message,
+ MessageCreateEvent,
+ NewUrlSignatureData,
+ NewUrlUserSignatureData,
+ ReadState,
+ Relationship,
+ Rights,
+ Snowflake,
+ stringGlobToRegexp,
+ uploadFile,
+ User,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@@ -48,17 +48,17 @@ import multer from "multer";
import { FindManyOptions, FindOperator, LessThan, MoreThan, MoreThanOrEqual } from "typeorm";
import { URL } from "url";
import {
- AcknowledgeDeleteSchema,
- AutomodCustomWordsRule,
- AutomodRuleActionType,
- AutomodRuleEventType,
- isTextChannel,
- MessageCreateAttachment,
- MessageCreateCloudAttachment,
- MessageCreateSchema,
- Reaction,
- ReadStateType,
- RelationshipType,
+ AcknowledgeDeleteSchema,
+ AutomodCustomWordsRule,
+ AutomodRuleActionType,
+ AutomodRuleEventType,
+ isTextChannel,
+ MessageCreateAttachment,
+ MessageCreateCloudAttachment,
+ MessageCreateSchema,
+ Reaction,
+ ReadStateType,
+ RelationshipType,
} from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
@@ -66,221 +66,221 @@ const router: Router = Router({ mergeParams: true });
// https://discord.com/developers/docs/resources/channel#create-message
// get messages
router.get(
- "/",
- route({
- query: {
- around: {
- type: "string",
- },
- before: {
- type: "string",
- },
- after: {
- type: "string",
- },
- limit: {
- type: "number",
- description: "max number of messages to return (1-100). defaults to 50",
- },
- },
- responses: {
- 200: {
- body: "APIMessageArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel) throw new HTTPError("Channel not found", 404);
+ "/",
+ route({
+ query: {
+ around: {
+ type: "string",
+ },
+ before: {
+ type: "string",
+ },
+ after: {
+ type: "string",
+ },
+ limit: {
+ type: "number",
+ description: "max number of messages to return (1-100). defaults to 50",
+ },
+ },
+ responses: {
+ 200: {
+ body: "APIMessageArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const channel_id = req.params.channel_id;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel) throw new HTTPError("Channel not found", 404);
- isTextChannel(channel.type);
- 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", 422);
+ isTextChannel(channel.type);
+ 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", 422);
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
- permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
+ const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
+ permissions.hasThrow("VIEW_CHANNEL");
+ if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
- const query: FindManyOptions & {
- where: { id?: FindOperator | FindOperator[] };
- } = {
- order: { timestamp: "DESC" },
- take: limit,
- where: { channel_id },
- relations: [
- "author",
- "webhook",
- "application",
- "mentions",
- "mention_roles",
- "mention_channels",
- "sticker_items",
- "attachments",
- "referenced_message",
- "referenced_message.author",
- "referenced_message.webhook",
- "referenced_message.application",
- "referenced_message.mentions",
- "referenced_message.mention_roles",
- "referenced_message.mention_channels",
- "referenced_message.sticker_items",
- "referenced_message.attachments",
- ],
- };
+ const query: FindManyOptions & {
+ where: { id?: FindOperator | FindOperator[] };
+ } = {
+ order: { timestamp: "DESC" },
+ take: limit,
+ where: { channel_id },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ "referenced_message",
+ "referenced_message.author",
+ "referenced_message.webhook",
+ "referenced_message.application",
+ "referenced_message.mentions",
+ "referenced_message.mention_roles",
+ "referenced_message.mention_channels",
+ "referenced_message.sticker_items",
+ "referenced_message.attachments",
+ ],
+ };
- let messages: Message[];
+ let messages: Message[];
- if (around) {
- query.take = Math.floor(limit / 2);
- if (query.take != 0) {
- const [right, left] = await Promise.all([
- Message.find({
- ...query,
- where: { channel_id, id: LessThan(around) },
- }),
- Message.find({
- ...query,
- where: { channel_id, id: MoreThanOrEqual(around) },
- order: { timestamp: "ASC" },
- }),
- ]);
- left.push(...right);
- messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
- } else {
- query.take = 1;
- const message = await Message.findOne({
- ...query,
- where: { channel_id, id: around },
- });
- messages = message ? [message] : [];
- }
- } else {
- if (after) {
- if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422);
+ if (around) {
+ query.take = Math.floor(limit / 2);
+ if (query.take != 0) {
+ const [right, left] = await Promise.all([
+ Message.find({
+ ...query,
+ where: { channel_id, id: LessThan(around) },
+ }),
+ Message.find({
+ ...query,
+ where: { channel_id, id: MoreThanOrEqual(around) },
+ order: { timestamp: "ASC" },
+ }),
+ ]);
+ left.push(...right);
+ messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
+ } else {
+ query.take = 1;
+ const message = await Message.findOne({
+ ...query,
+ where: { channel_id, id: around },
+ });
+ messages = message ? [message] : [];
+ }
+ } else {
+ if (after) {
+ if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422);
- query.where.id = MoreThan(after);
- query.order = { timestamp: "ASC" };
- } else if (before) {
- if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422);
+ query.where.id = MoreThan(after);
+ query.order = { timestamp: "ASC" };
+ } else if (before) {
+ if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422);
- query.where.id = LessThan(before);
- }
+ query.where.id = LessThan(before);
+ }
- messages = await Message.find(query);
- }
+ messages = await Message.find(query);
+ }
- const endpoint = Config.get().cdn.endpointPublic;
+ const endpoint = Config.get().cdn.endpointPublic;
- const ret = messages.map((x: Message) => {
- x = x.toJSON();
+ const ret = messages.map((x: Message) => {
+ x = x.toJSON();
- (x.reactions || []).forEach((y: Partial) => {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- if ((y.user_ids || []).includes(req.user_id)) y.me = true;
- delete y.user_ids;
- });
- if (!x.author)
- x.author = User.create({
- id: "4",
- discriminator: "0000",
- username: "Spacebar Ghost",
- public_flags: 0,
- });
- x.attachments?.forEach((y: Attachment) => {
- // dynamically set attachment proxy_url in case the endpoint changed
- const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
+ (x.reactions || []).forEach((y: Partial) => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ if ((y.user_ids || []).includes(req.user_id)) y.me = true;
+ delete y.user_ids;
+ });
+ if (!x.author)
+ x.author = User.create({
+ id: "4",
+ discriminator: "0000",
+ username: "Spacebar Ghost",
+ public_flags: 0,
+ });
+ x.attachments?.forEach((y: Attachment) => {
+ // dynamically set attachment proxy_url in case the endpoint changed
+ const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
- const url = new URL(uri);
- if (endpoint) {
- const newBase = new URL(endpoint);
- url.protocol = newBase.protocol;
- url.hostname = newBase.hostname;
- url.port = newBase.port;
- }
+ const url = new URL(uri);
+ if (endpoint) {
+ const newBase = new URL(endpoint);
+ url.protocol = newBase.protocol;
+ url.hostname = newBase.hostname;
+ url.port = newBase.port;
+ }
- y.proxy_url = url.toString();
+ y.proxy_url = url.toString();
- y.proxy_url = getUrlSignature(
- new NewUrlSignatureData({
- url: y.proxy_url,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- }),
- )
- .applyToUrl(y.proxy_url)
- .toString();
+ y.proxy_url = getUrlSignature(
+ new NewUrlSignatureData({
+ url: y.proxy_url,
+ userAgent: req.headers["user-agent"],
+ ip: req.ip,
+ }),
+ )
+ .applyToUrl(y.proxy_url)
+ .toString();
- y.url = getUrlSignature(
- new NewUrlSignatureData({
- url: y.url,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- }),
- )
- .applyToUrl(y.url)
- .toString();
- });
+ y.url = getUrlSignature(
+ new NewUrlSignatureData({
+ url: y.url,
+ userAgent: req.headers["user-agent"],
+ ip: req.ip,
+ }),
+ )
+ .applyToUrl(y.url)
+ .toString();
+ });
- /**
+ /**
Some clients ( discord.js ) only check if a property exists within the response,
which causes errors when, say, the `application` property is `null`.
**/
- // for (var curr in x) {
- // if (x[curr] === null)
- // delete x[curr];
- // }
+ // for (var curr in x) {
+ // if (x[curr] === null)
+ // delete x[curr];
+ // }
- return x;
- });
+ return x;
+ });
- await Promise.all(
- ret
- .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
- .map(async (x: MessageCreateSchema) => {
- x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } });
- }),
- );
+ await Promise.all(
+ ret
+ .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
+ .map(async (x: MessageCreateSchema) => {
+ x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } });
+ }),
+ );
- // polyfill message references for old messages
- await Promise.all(
- ret
- .filter((msg) => msg.message_reference && !msg.referenced_message?.id)
- .map(async (msg) => {
- const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
- id: msg.message_reference!.message_id,
- };
- if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
- if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
+ // polyfill message references for old messages
+ await Promise.all(
+ ret
+ .filter((msg) => msg.message_reference && !msg.referenced_message?.id)
+ .map(async (msg) => {
+ const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
+ id: msg.message_reference!.message_id,
+ };
+ if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
+ if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
- msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
- }),
- );
+ msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
+ }),
+ );
- return res.json(ret);
- },
+ return res.json(ret);
+ },
);
// TODO: config max upload size
const messageUpload = multer({
- limits: {
- fileSize: Config.get().limits.message.maxAttachmentSize,
- fields: 10,
- // files: 1
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: Config.get().limits.message.maxAttachmentSize,
+ fields: 10,
+ // files: 1
+ },
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
/**
TODO: dynamically change limit of MessageCreateSchema with config
@@ -292,213 +292,213 @@ const messageUpload = multer({
**/
// Send message
router.post(
- "/",
- messageUpload.any(),
- (req, res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
+ "/",
+ messageUpload.any(),
+ (req, res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- next();
- },
- route({
- requestBody: "MessageCreateSchema",
- permission: "SEND_MESSAGES",
- right: "SEND_MESSAGES",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const body = req.body as MessageCreateSchema;
- const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
+ next();
+ },
+ route({
+ requestBody: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const body = req.body as MessageCreateSchema;
+ const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients", "recipients.user"],
- });
- if (!channel.isWritable()) {
- throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
- }
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
+ if (!channel.isWritable()) {
+ throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
+ }
- // handle blocked users in dms
- if (channel.recipients?.length == 2) {
- const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user;
- if (otherUser) {
- const relationship = await Relationship.findOne({
- where: [
- { from_id: req.user_id, to_id: otherUser.id },
- { from_id: otherUser.id, to_id: req.user_id },
- ],
- });
+ // handle blocked users in dms
+ if (channel.recipients?.length == 2) {
+ const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user;
+ if (otherUser) {
+ const relationship = await Relationship.findOne({
+ where: [
+ { from_id: req.user_id, to_id: otherUser.id },
+ { from_id: otherUser.id, to_id: req.user_id },
+ ],
+ });
- if (relationship?.type === RelationshipType.blocked) {
- throw DiscordApiErrors.CANNOT_MESSAGE_USER;
- }
- }
- }
+ if (relationship?.type === RelationshipType.blocked) {
+ throw DiscordApiErrors.CANNOT_MESSAGE_USER;
+ }
+ }
+ }
- if (body.nonce) {
- const existing = await Message.findOne({
- where: {
- nonce: body.nonce,
- channel_id: channel.id,
- author_id: req.user_id,
- },
- });
- if (existing) {
- return res.json(existing);
- }
- }
+ if (body.nonce) {
+ const existing = await Message.findOne({
+ where: {
+ nonce: body.nonce,
+ channel_id: channel.id,
+ author_id: req.user_id,
+ },
+ });
+ if (existing) {
+ return res.json(existing);
+ }
+ }
- if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) {
- const limits = Config.get().limits;
- if (limits.absoluteRate.sendMessage.enabled) {
- const count = await Message.count({
- where: {
- channel_id,
- timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
- },
- });
+ if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) {
+ const limits = Config.get().limits;
+ if (limits.absoluteRate.sendMessage.enabled) {
+ const count = await Message.count({
+ where: {
+ channel_id,
+ timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
+ },
+ });
- if (count >= limits.absoluteRate.sendMessage.limit)
- throw FieldErrors({
- channel_id: {
- code: "TOO_MANY_MESSAGES",
- message: req.t("common:toomany.MESSAGE"),
- },
- });
- }
- }
+ if (count >= limits.absoluteRate.sendMessage.limit)
+ throw FieldErrors({
+ channel_id: {
+ code: "TOO_MANY_MESSAGES",
+ message: req.t("common:toomany.MESSAGE"),
+ },
+ });
+ }
+ }
- const files = (req.files as Express.Multer.File[]) ?? [];
- for (const currFile of files) {
- try {
- const file = await uploadFile(`/attachments/${channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- } catch (error) {
- return res.status(400).json({ message: error?.toString() });
- }
- }
+ const files = (req.files as Express.Multer.File[]) ?? [];
+ for (const currFile of files) {
+ try {
+ const file = await uploadFile(`/attachments/${channel.id}`, currFile);
+ attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ } catch (error) {
+ return res.status(400).json({ message: error?.toString() });
+ }
+ }
- const embeds = body.embeds || [];
- if (body.embed) embeds.push(body.embed);
- const message = await handleMessage({
- ...body,
- type: 0,
- pinned: false,
- author_id: req.user_id,
- embeds,
- channel_id,
- attachments,
- timestamp: new Date(),
- });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore dont care2
- message.edited_timestamp = null;
+ const embeds = body.embeds || [];
+ if (body.embed) embeds.push(body.embed);
+ const message = await handleMessage({
+ ...body,
+ type: 0,
+ pinned: false,
+ author_id: req.user_id,
+ embeds,
+ channel_id,
+ attachments,
+ timestamp: new Date(),
+ });
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore dont care2
+ message.edited_timestamp = null;
- channel.last_message_id = message.id;
+ channel.last_message_id = message.id;
- if (channel.isDm()) {
- const channel_dto = await DmChannelDTO.from(channel);
+ if (channel.isDm()) {
+ const channel_dto = await DmChannelDTO.from(channel);
- // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
- await Promise.all(
- channel.recipients?.map((recipient) => {
- if (recipient.closed) {
- recipient.closed = false;
- return Promise.all([
- recipient.save(),
- emitEvent({
- event: "CHANNEL_CREATE",
- data: channel_dto.excludedRecipients([recipient.user_id]),
- user_id: recipient.user_id,
- }),
- ]);
- }
- }) || [],
- );
- }
+ // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
+ await Promise.all(
+ channel.recipients?.map((recipient) => {
+ if (recipient.closed) {
+ recipient.closed = false;
+ return Promise.all([
+ recipient.save(),
+ emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto.excludedRecipients([recipient.user_id]),
+ user_id: recipient.user_id,
+ }),
+ ]);
+ }
+ }) || [],
+ );
+ }
- if (message.guild_id) {
- // handleMessage will fetch the Member, but only if they are not guild owner.
- // have to fetch ourselves otherwise.
- if (!message.member) {
- message.member = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: message.guild_id },
- relations: ["roles"],
- });
- }
+ if (message.guild_id) {
+ // handleMessage will fetch the Member, but only if they are not guild owner.
+ // have to fetch ourselves otherwise.
+ if (!message.member) {
+ message.member = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: message.guild_id },
+ relations: ["roles"],
+ });
+ }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id);
- }
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id);
+ }
- let read_state = await ReadState.findOne({
- where: { user_id: req.user_id, channel_id },
- });
- if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
- read_state.last_message_id = message.id;
- //It's a little more complicated than this but this'll do
- read_state.mention_count = 0;
+ let read_state = await ReadState.findOne({
+ where: { user_id: req.user_id, channel_id },
+ });
+ if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
+ read_state.last_message_id = message.id;
+ //It's a little more complicated than this but this'll do
+ read_state.mention_count = 0;
- await Promise.all([
- read_state.save(),
- message.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: channel_id,
- data: message,
- } as MessageCreateEvent),
- message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null,
- channel.save(),
- ]);
+ await Promise.all([
+ read_state.save(),
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null,
+ channel.save(),
+ ]);
- // no await as it shouldnt block the message send function and silently catch error
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- return res.json(
- message.withSignedAttachments(
- new NewUrlUserSignatureData({
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- ),
- );
- },
+ return res.json(
+ message.withSignedAttachments(
+ new NewUrlUserSignatureData({
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ ),
+ );
+ },
);
router.delete(
- "/ack",
- route({
- requestBody: "AcknowledgeDeleteSchema",
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL
- const body = req.body as AcknowledgeDeleteSchema;
- if (body.version != 2) return res.status(204).send();
- // TODO: handle other read state types
- if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send();
+ "/ack",
+ route({
+ requestBody: "AcknowledgeDeleteSchema",
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL
+ const body = req.body as AcknowledgeDeleteSchema;
+ if (body.version != 2) return res.status(204).send();
+ // TODO: handle other read state types
+ if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send();
- const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } });
- if (readState) {
- await readState.remove();
- }
+ const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } });
+ if (readState) {
+ await readState.remove();
+ }
- res.status(204).send();
- },
+ res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/pins/index.ts b/src/api/routes/channels/#channel_id/messages/pins/index.ts
index 1d770bd29..2355c8b99 100644
--- a/src/api/routes/channels/#channel_id/messages/pins/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/pins/index.ts
@@ -24,169 +24,169 @@ import { IsNull, Not } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.put(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- // * in dm channels anyone can pin messages -> only check for guilds
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ // * in dm channels anyone can pin messages -> only check for guilds
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- const pinned_count = await Message.count({
- where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
- });
+ const pinned_count = await Message.count({
+ where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
+ });
- const { maxPins } = Config.get().limits.channel;
- if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
+ const { maxPins } = Config.get().limits.channel;
+ if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
- message.pinned_at = new Date();
+ message.pinned_at = new Date();
- const author = await User.getPublicUser(req.user_id);
+ const author = await User.getPublicUser(req.user_id);
- const systemPinMessage = Message.create({
- timestamp: new Date(),
- type: 6,
- guild_id: message.guild_id,
- channel_id: message.channel_id,
- author,
- message_reference: {
- message_id: message.id,
- channel_id: message.channel_id,
- guild_id: message.guild_id,
- },
- reactions: [],
- attachments: [],
- embeds: [],
- sticker_items: [],
- edited_timestamp: undefined,
- mentions: [],
- mention_channels: [],
- mention_roles: [],
- mention_everyone: false,
- });
+ const systemPinMessage = Message.create({
+ timestamp: new Date(),
+ type: 6,
+ guild_id: message.guild_id,
+ channel_id: message.channel_id,
+ author,
+ message_reference: {
+ message_id: message.id,
+ channel_id: message.channel_id,
+ guild_id: message.guild_id,
+ },
+ reactions: [],
+ attachments: [],
+ embeds: [],
+ sticker_items: [],
+ edited_timestamp: undefined,
+ mentions: [],
+ mention_channels: [],
+ mention_roles: [],
+ mention_everyone: false,
+ });
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- systemPinMessage.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: message.channel_id,
- data: systemPinMessage,
- } as MessageCreateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ systemPinMessage.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: message.channel_id,
+ data: systemPinMessage,
+ } as MessageCreateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- message.pinned_at = null;
+ message.pinned_at = null;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.get(
- "/",
- route({
- permission: ["READ_MESSAGE_HISTORY"],
- responses: {
- 200: {
- body: "APIMessageArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: ["READ_MESSAGE_HISTORY"],
+ responses: {
+ 200: {
+ body: "APIMessageArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const pins = await Message.find({
- where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
- relations: ["author"],
- order: { pinned_at: "DESC" },
- });
+ const pins = await Message.find({
+ where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
+ relations: ["author"],
+ order: { pinned_at: "DESC" },
+ });
- const items = pins.map((message: Message) => ({
- message,
- pinned_at: message.pinned_at,
- }));
+ const items = pins.map((message: Message) => ({
+ message,
+ pinned_at: message.pinned_at,
+ }));
- res.send({
- items,
- has_more: false,
- });
- },
+ res.send({
+ items,
+ has_more: false,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/search.ts b/src/api/routes/channels/#channel_id/messages/search.ts
index 7331945d6..bec9a44c1 100644
--- a/src/api/routes/channels/#channel_id/messages/search.ts
+++ b/src/api/routes/channels/#channel_id/messages/search.ts
@@ -27,116 +27,116 @@ import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildMessagesSearchResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 422: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { guild_id: req.params.guild_id },
- select: ["id"],
- });
- const {
- content,
- // include_nsfw, // TODO
- offset,
- sort_order,
- // sort_by, // TODO: Handle 'relevance'
- limit,
- author_id,
- } = req.query;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildMessagesSearchResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 422: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id: req.params.guild_id },
+ select: ["id"],
+ });
+ const {
+ content,
+ // include_nsfw, // TODO
+ offset,
+ sort_order,
+ // sort_by, // TODO: Handle 'relevance'
+ limit,
+ author_id,
+ } = req.query;
- const parsedLimit = Number(limit) || 50;
- if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
+ const parsedLimit = Number(limit) || 50;
+ if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
- if (sort_order) {
- if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1)
- throw FieldErrors({
- sort_order: {
- message: "Value must be one of ('desc', 'asc').",
- code: "BASE_TYPE_CHOICES",
- },
- }); // todo this is wrong
- }
+ if (sort_order) {
+ if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1)
+ throw FieldErrors({
+ sort_order: {
+ message: "Value must be one of ('desc', 'asc').",
+ code: "BASE_TYPE_CHOICES",
+ },
+ }); // todo this is wrong
+ }
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id as string | undefined);
- permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
+ const permissions = await getPermission(req.user_id, channel.guild_id, channel_id as string | undefined);
+ permissions.hasThrow("VIEW_CHANNEL");
+ if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
- const query: FindManyOptions = {
- order: {
- timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
- },
- take: parsedLimit || 0,
- where: {
- guild: {
- id: channel.guild_id,
- },
- channel: {
- id: channel_id,
- },
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- skip: offset ? Number(offset) : 0,
- };
- //@ts-ignore
- query.where.channel = { id: channel_id };
+ const query: FindManyOptions = {
+ order: {
+ timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
+ },
+ take: parsedLimit || 0,
+ where: {
+ guild: {
+ id: channel.guild_id,
+ },
+ channel: {
+ id: channel_id,
+ },
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ skip: offset ? Number(offset) : 0,
+ };
+ //@ts-ignore
+ query.where.channel = { id: channel_id };
- //@ts-ignore
- if (author_id) query.where.author = { id: author_id };
- //@ts-ignore
- if (content) query.where.content = Like(`%${content}%`);
+ //@ts-ignore
+ if (author_id) query.where.author = { id: author_id };
+ //@ts-ignore
+ if (content) query.where.content = Like(`%${content}%`);
- const messages: Message[] = await Message.find(query);
- delete query.take;
- const total_results = await Message.count(query);
+ const messages: Message[] = await Message.find(query);
+ delete query.take;
+ const total_results = await Message.count(query);
- const messagesDto = messages.map((x) => [
- {
- id: x.id,
- type: x.type,
- content: x.content,
- channel_id: x.channel_id,
- author: {
- id: x.author?.id,
- username: x.author?.username,
- avatar: x.author?.avatar,
- avatar_decoration: null,
- discriminator: x.author?.discriminator,
- public_flags: x.author?.public_flags,
- },
- attachments: x.attachments,
- embeds: x.embeds,
- mentions: x.mentions,
- mention_roles: x.mention_roles,
- pinned: x.pinned,
- mention_everyone: x.mention_everyone,
- tts: x.tts,
- timestamp: x.timestamp,
- edited_timestamp: x.edited_timestamp,
- flags: x.flags,
- components: x.components,
- poll: x.poll,
- hit: true,
- },
- ]);
+ const messagesDto = messages.map((x) => [
+ {
+ id: x.id,
+ type: x.type,
+ content: x.content,
+ channel_id: x.channel_id,
+ author: {
+ id: x.author?.id,
+ username: x.author?.username,
+ avatar: x.author?.avatar,
+ avatar_decoration: null,
+ discriminator: x.author?.discriminator,
+ public_flags: x.author?.public_flags,
+ },
+ attachments: x.attachments,
+ embeds: x.embeds,
+ mentions: x.mentions,
+ mention_roles: x.mention_roles,
+ pinned: x.pinned,
+ mention_everyone: x.mention_everyone,
+ tts: x.tts,
+ timestamp: x.timestamp,
+ edited_timestamp: x.edited_timestamp,
+ flags: x.flags,
+ components: x.components,
+ poll: x.poll,
+ hit: true,
+ },
+ ]);
- return res.json({
- messages: messagesDto,
- total_results,
- });
- },
+ return res.json({
+ messages: messagesDto,
+ total_results,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index b45ff1444..c03ca03f5 100644
--- a/src/api/routes/channels/#channel_id/permissions.ts
+++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -27,80 +27,80 @@ const router: Router = Router({ mergeParams: true });
// 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",
- route({
- requestBody: "ChannelPermissionOverwriteSchema",
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 404: {},
- 501: {},
- 400: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, overwrite_id } = req.params;
- const body = req.body as ChannelPermissionOverwriteSchema;
+ "/:overwrite_id",
+ route({
+ requestBody: "ChannelPermissionOverwriteSchema",
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 404: {},
+ 501: {},
+ 400: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, overwrite_id } = req.params;
+ const body = req.body as ChannelPermissionOverwriteSchema;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
- channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
+ channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
- if (body.type === ChannelPermissionOverwriteType.role) {
- if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
- } else if (body.type === ChannelPermissionOverwriteType.member) {
- if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
- } else throw new HTTPError("type not supported", 501);
+ if (body.type === ChannelPermissionOverwriteType.role) {
+ if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
+ } else if (body.type === ChannelPermissionOverwriteType.member) {
+ if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
+ } else throw new HTTPError("type not supported", 501);
- let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id);
- if (!overwrite) {
- overwrite = {
- id: overwrite_id,
- type: body.type,
- allow: "0",
- deny: "0",
- };
- channel.permission_overwrites?.push(overwrite);
- }
- overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0")));
- overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0")));
+ let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id);
+ if (!overwrite) {
+ overwrite = {
+ id: overwrite_id,
+ type: body.type,
+ allow: "0",
+ deny: "0",
+ };
+ channel.permission_overwrites?.push(overwrite);
+ }
+ overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0")));
+ overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0")));
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- channel_id,
- data: channel,
- } as ChannelUpdateEvent),
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ channel_id,
+ data: channel,
+ } as ChannelUpdateEvent),
+ ]);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
// TODO: check permission hierarchy
router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }), async (req: Request, res: Response) => {
- const { channel_id, overwrite_id } = req.params;
+ const { channel_id, overwrite_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
+ const channel = await Channel.findOneOrFail({
+ where: { 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(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- channel_id,
- data: channel,
- } as ChannelUpdateEvent),
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ channel_id,
+ data: channel,
+ } as ChannelUpdateEvent),
+ ]);
- return res.sendStatus(204);
+ return res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index 4b62115f7..8e56a54d5 100644
--- a/src/api/routes/channels/#channel_id/pins.ts
+++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -25,161 +25,161 @@ const router: Router = Router({ mergeParams: true });
// This is the old endpoint
router.put(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- // * in dm channels anyone can pin messages -> only check for guilds
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ // * in dm channels anyone can pin messages -> only check for guilds
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- const pinned_count = await Message.count({
- where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
- });
+ const pinned_count = await Message.count({
+ where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
+ });
- const { maxPins } = Config.get().limits.channel;
- if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
+ const { maxPins } = Config.get().limits.channel;
+ if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
- message.pinned_at = new Date();
+ message.pinned_at = new Date();
- const author = await User.getPublicUser(req.user_id);
+ const author = await User.getPublicUser(req.user_id);
- const systemPinMessage = Message.create({
- timestamp: new Date(),
- type: 6,
- guild_id: message.guild_id,
- channel_id: message.channel_id,
- author,
- message_reference: {
- message_id: message.id,
- channel_id: message.channel_id,
- guild_id: message.guild_id,
- },
- reactions: [],
- attachments: [],
- embeds: [],
- sticker_items: [],
- edited_timestamp: undefined,
- mentions: [],
- mention_channels: [],
- mention_roles: [],
- mention_everyone: false,
- });
+ const systemPinMessage = Message.create({
+ timestamp: new Date(),
+ type: 6,
+ guild_id: message.guild_id,
+ channel_id: message.channel_id,
+ author,
+ message_reference: {
+ message_id: message.id,
+ channel_id: message.channel_id,
+ guild_id: message.guild_id,
+ },
+ reactions: [],
+ attachments: [],
+ embeds: [],
+ sticker_items: [],
+ edited_timestamp: undefined,
+ mentions: [],
+ mention_channels: [],
+ mention_roles: [],
+ mention_everyone: false,
+ });
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- systemPinMessage.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: message.channel_id,
- data: systemPinMessage,
- } as MessageCreateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ systemPinMessage.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: message.channel_id,
+ data: systemPinMessage,
+ } as MessageCreateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- message.pinned_at = null;
+ message.pinned_at = null;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.get(
- "/",
- route({
- permission: ["READ_MESSAGE_HISTORY"],
- responses: {
- 200: {
- body: "APIMessageArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: ["READ_MESSAGE_HISTORY"],
+ responses: {
+ 200: {
+ body: "APIMessageArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const pins = await Message.find({
- where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
- relations: ["author"],
- order: { pinned_at: "DESC" },
- });
+ const pins = await Message.find({
+ where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
+ relations: ["author"],
+ order: { pinned_at: "DESC" },
+ });
- res.send(pins);
- },
+ res.send(pins);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts
index 9686bc20c..3a46beb7d 100644
--- a/src/api/routes/channels/#channel_id/purge.ts
+++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -31,71 +31,71 @@ export default router;
TODO: apply the delete bit by bit to prevent client and database stress
**/
router.post(
- "/",
- route({
- /*body: "PurgeSchema",*/
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ "/",
+ route({
+ /*body: "PurgeSchema",*/
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400);
- isTextChannel(channel.type);
+ if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400);
+ isTextChannel(channel.type);
- const rights = await getRights(req.user_id);
- if (!rights.has("MANAGE_MESSAGES")) {
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- permissions.hasThrow("MANAGE_CHANNELS");
- }
+ const rights = await getRights(req.user_id);
+ if (!rights.has("MANAGE_MESSAGES")) {
+ const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
+ permissions.hasThrow("MANAGE_MESSAGES");
+ permissions.hasThrow("MANAGE_CHANNELS");
+ }
- const { before, after } = req.body as PurgeSchema;
+ const { before, after } = req.body as PurgeSchema;
- // TODO: send the deletion event bite-by-bite to prevent client stress
+ // TODO: send the deletion event bite-by-bite to prevent client stress
- const query: FindManyOptions & {
- where: { id?: FindOperator };
- } = {
- order: { id: "ASC" },
- // take: limit,
- where: {
- channel_id,
- id: Between(after, before), // the right way around
- author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id),
- // if you lack the right of self-deletion, you can't delete your own messages, even in purges
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- };
+ const query: FindManyOptions & {
+ where: { id?: FindOperator };
+ } = {
+ order: { id: "ASC" },
+ // take: limit,
+ where: {
+ channel_id,
+ id: Between(after, before), // the right way around
+ author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id),
+ // if you lack the right of self-deletion, you can't delete your own messages, even in purges
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ };
- const messages = await Message.find(query);
+ const messages = await Message.find(query);
- if (messages.length == 0) {
- res.sendStatus(304);
- return;
- }
+ if (messages.length == 0) {
+ res.sendStatus(304);
+ return;
+ }
- await Message.delete(messages.map((x) => x.id));
+ await Message.delete(messages.map((x) => x.id));
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: {
- ids: messages.map((x) => x.id),
- channel_id,
- guild_id: channel.guild_id,
- },
- } as MessageDeleteBulkEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: {
+ ids: messages.map((x) => x.id),
+ channel_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageDeleteBulkEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index 6388cfe21..d0af3b4a0 100644
--- a/src/api/routes/channels/#channel_id/recipients.ts
+++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -24,79 +24,79 @@ import { ChannelType, PublicUserProjection } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.put(
- "/:user_id",
- route({
- responses: {
- 201: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
+ "/:user_id",
+ route({
+ responses: {
+ 201: {},
+ 404: {},
+ },
+ }),
+ 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 = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])];
+ if (channel.type !== ChannelType.GROUP_DM) {
+ const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), 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)) {
- throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
- }
+ 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)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
+ }
- channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
- await channel.save();
+ channel.recipients?.push(Recipient.create({ 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_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);
- }
- },
+ 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",
- route({
- responses: {
- 204: {},
- 404: {},
- },
- }),
- 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;
+ "/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 404: {},
+ },
+ }),
+ 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?
- }
+ if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
+ }
- await Channel.removeRecipientFromChannel(channel, user_id);
+ await Channel.removeRecipientFromChannel(channel, user_id);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 8c36add1d..a41ab7ff1 100644
--- a/src/api/routes/channels/#channel_id/typing.ts
+++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -23,47 +23,47 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- permission: "SEND_MESSAGES",
- responses: {
- 204: {},
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const user_id = req.user_id;
- const timestamp = Math.floor(Date.now() / 1000);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const member = await Member.findOne({
- where: { id: user_id, guild_id: channel.guild_id },
- relations: ["roles", "user"],
- });
- await emitEvent({
- event: "TYPING_START",
- channel_id: channel_id,
- data: {
- ...(member
- ? {
- member: {
- ...member.toPublicMember(),
- roles: member?.roles?.map((x) => x.id),
- },
- }
- : null),
- channel_id,
- timestamp,
- user_id,
- guild_id: channel.guild_id,
- },
- } as TypingStartEvent);
+ "/",
+ route({
+ permission: "SEND_MESSAGES",
+ responses: {
+ 204: {},
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const user_id = req.user_id;
+ const timestamp = Math.floor(Date.now() / 1000);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const member = await Member.findOne({
+ where: { id: user_id, guild_id: channel.guild_id },
+ relations: ["roles", "user"],
+ });
+ await emitEvent({
+ event: "TYPING_START",
+ channel_id: channel_id,
+ data: {
+ ...(member
+ ? {
+ member: {
+ ...member.toPublicMember(),
+ roles: member?.roles?.map((x) => x.id),
+ },
+ }
+ : null),
+ channel_id,
+ timestamp,
+ user_id,
+ guild_id: channel.guild_id,
+ },
+ } as TypingStartEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 9d226b19a..1c3c980a5 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -26,88 +26,88 @@ import { isTextChannel, WebhookCreateSchema, WebhookType } from "@spacebar/schem
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.",
- permission: "MANAGE_WEBHOOKS",
- responses: {
- 200: {
- body: "APIWebhookArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const webhooks = await Webhook.find({
- where: { channel_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.",
+ permission: "MANAGE_WEBHOOKS",
+ responses: {
+ 200: {
+ body: "APIWebhookArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const webhooks = await Webhook.find({
+ where: { channel_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- return res.json(
- webhooks.map((webhook) => ({
- ...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
- })),
- );
- },
+ return res.json(
+ webhooks.map((webhook) => ({
+ ...webhook,
+ url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ })),
+ );
+ },
);
// TODO: use Image Data Type for avatar instead of String
router.post(
- "/",
- route({
- requestBody: "WebhookCreateSchema",
- permission: "MANAGE_WEBHOOKS",
- responses: {
- 200: {
- body: "WebhookCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ "/",
+ route({
+ requestBody: "WebhookCreateSchema",
+ permission: "MANAGE_WEBHOOKS",
+ responses: {
+ 200: {
+ body: "WebhookCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const channel_id = req.params.channel_id;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- isTextChannel(channel.type);
- if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
+ isTextChannel(channel.type);
+ if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
- const webhook_count = await Webhook.count({ where: { channel_id } });
- const { maxWebhooks } = Config.get().limits.channel;
- if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
+ const webhook_count = await Webhook.count({ where: { channel_id } });
+ const { maxWebhooks } = Config.get().limits.channel;
+ if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
- let { avatar, name } = req.body as WebhookCreateSchema;
- name = trimSpecial(name);
+ let { avatar, name } = req.body as WebhookCreateSchema;
+ name = trimSpecial(name);
- // TODO: move this
- if (name) {
- ValidateName(name);
- }
+ // TODO: move this
+ if (name) {
+ ValidateName(name);
+ }
- if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
+ if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
- const hook = await Webhook.create({
- type: WebhookType.Incoming,
- name,
- avatar,
- guild_id: channel.guild_id,
- channel_id: channel.id,
- user_id: req.user_id,
- token: crypto.randomBytes(24).toString("base64url"),
- }).save();
+ const hook = await Webhook.create({
+ type: WebhookType.Incoming,
+ name,
+ avatar,
+ guild_id: channel.guild_id,
+ channel_id: channel.id,
+ user_id: req.user_id,
+ token: crypto.randomBytes(24).toString("base64url"),
+ }).save();
- const user = await User.getPublicUser(req.user_id);
+ const user = await User.getPublicUser(req.user_id);
- return res.json({
- ...hook,
- user: user,
- });
- },
+ return res.json({
+ ...hook,
+ user: user,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/channels/preload-messages.ts b/src/api/routes/channels/preload-messages.ts
index cdfc58f06..6a1905243 100644
--- a/src/api/routes/channels/preload-messages.ts
+++ b/src/api/routes/channels/preload-messages.ts
@@ -23,49 +23,49 @@ import { PreloadMessagesRequestSchema, PreloadMessagesResponseSchema } from "@sp
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "PreloadMessagesRequestSchema",
- responses: {
- 200: {
- body: "PreloadMessagesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as PreloadMessagesRequestSchema;
- if (body.channels.length > Config.get().limits.message.maxPreloadCount)
- return res.status(400).send({
- code: 400,
- message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`,
- });
+ "/",
+ route({
+ requestBody: "PreloadMessagesRequestSchema",
+ responses: {
+ 200: {
+ body: "PreloadMessagesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as PreloadMessagesRequestSchema;
+ if (body.channels.length > Config.get().limits.message.maxPreloadCount)
+ return res.status(400).send({
+ code: 400,
+ message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`,
+ });
- const messages = (
- await Promise.all(
- body.channels.map(
- async (channelId) =>
- await Message.findOne({
- where: { channel_id: channelId },
- order: { timestamp: "DESC" },
- }),
- ),
- )
- ).filter((x) => x !== null) as Message[];
+ const messages = (
+ await Promise.all(
+ body.channels.map(
+ async (channelId) =>
+ await Message.findOne({
+ where: { channel_id: channelId },
+ order: { timestamp: "DESC" },
+ }),
+ ),
+ )
+ ).filter((x) => x !== null) as Message[];
- const filteredMessages = messages.map((message) => {
- const x = message.toJSON();
- // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- x.reactions = undefined;
- return x;
- }) as PreloadMessagesResponseSchema;
+ const filteredMessages = messages.map((message) => {
+ const x = message.toJSON();
+ // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-expect-error
+ x.reactions = undefined;
+ return x;
+ }) as PreloadMessagesResponseSchema;
- return res.status(200).send(filteredMessages);
- },
+ return res.status(200).send(filteredMessages);
+ },
);
export default router;
diff --git a/src/api/routes/collectibles-categories.ts b/src/api/routes/collectibles-categories.ts
index d2cac3962..ffeff099b 100644
--- a/src/api/routes/collectibles-categories.ts
+++ b/src/api/routes/collectibles-categories.ts
@@ -23,18 +23,18 @@ import { CollectiblesCategoriesResponse } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "CollectiblesCategoriesResponse",
- },
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- res.send([] as CollectiblesCategoriesResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "CollectiblesCategoriesResponse",
+ },
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ res.send([] as CollectiblesCategoriesResponse);
+ },
);
export default router;
diff --git a/src/api/routes/collectibles-shop.ts b/src/api/routes/collectibles-shop.ts
index b4c82f574..756c80c56 100644
--- a/src/api/routes/collectibles-shop.ts
+++ b/src/api/routes/collectibles-shop.ts
@@ -23,21 +23,21 @@ import { CollectiblesShopResponse } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "CollectiblesShopResponse",
- },
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- res.send({
- shop_blocks: [],
- categories: [],
- } as CollectiblesShopResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "CollectiblesShopResponse",
+ },
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ res.send({
+ shop_blocks: [],
+ categories: [],
+ } as CollectiblesShopResponse);
+ },
);
export default router;
diff --git a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
index 46f20aeef..4a1a1cb46 100644
--- a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
+++ b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
@@ -21,9 +21,9 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post("/", route({}), async (req: Request, res: Response) => {
- // TODO:
- // const { connection_name, connection_id } = req.params;
- res.sendStatus(204);
+ // TODO:
+ // const { connection_name, connection_id } = req.params;
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/connections/#connection_name/authorize.ts b/src/api/routes/connections/#connection_name/authorize.ts
index b8e50db6b..1efb6564b 100644
--- a/src/api/routes/connections/#connection_name/authorize.ts
+++ b/src/api/routes/connections/#connection_name/authorize.ts
@@ -23,28 +23,28 @@ import { ConnectionStore, FieldErrors } from "../../../../util";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { connection_name } = req.params;
- const connection = ConnectionStore.connections.get(connection_name);
- if (!connection)
- throw FieldErrors({
- provider_id: {
- code: "BASE_TYPE_CHOICES",
- message: req.t("common:field.BASE_TYPE_CHOICES", {
- types: Array.from(ConnectionStore.connections.keys()).join(", "),
- }),
- },
- });
+ const { connection_name } = req.params;
+ const connection = ConnectionStore.connections.get(connection_name);
+ if (!connection)
+ throw FieldErrors({
+ provider_id: {
+ code: "BASE_TYPE_CHOICES",
+ message: req.t("common:field.BASE_TYPE_CHOICES", {
+ types: Array.from(ConnectionStore.connections.keys()).join(", "),
+ }),
+ },
+ });
- if (!connection.settings.enabled)
- throw FieldErrors({
- provider_id: {
- message: "This connection has been disabled server-side.",
- },
- });
+ if (!connection.settings.enabled)
+ throw FieldErrors({
+ provider_id: {
+ message: "This connection has been disabled server-side.",
+ },
+ });
- res.json({
- url: await connection.getAuthorizationUrl(req.user_id),
- });
+ res.json({
+ url: await connection.getAuthorizationUrl(req.user_id),
+ });
});
export default router;
diff --git a/src/api/routes/connections/#connection_name/callback.ts b/src/api/routes/connections/#connection_name/callback.ts
index 5732a8e6e..a83882caa 100644
--- a/src/api/routes/connections/#connection_name/callback.ts
+++ b/src/api/routes/connections/#connection_name/callback.ts
@@ -24,38 +24,38 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post("/", route({ requestBody: "ConnectionCallbackSchema" }), async (req: Request, res: Response) => {
- const { connection_name } = req.params;
- const connection = ConnectionStore.connections.get(connection_name);
- if (!connection)
- throw FieldErrors({
- provider_id: {
- code: "BASE_TYPE_CHOICES",
- message: req.t("common:field.BASE_TYPE_CHOICES", {
- types: Array.from(ConnectionStore.connections.keys()).join(", "),
- }),
- },
- });
+ const { connection_name } = req.params;
+ const connection = ConnectionStore.connections.get(connection_name);
+ if (!connection)
+ throw FieldErrors({
+ provider_id: {
+ code: "BASE_TYPE_CHOICES",
+ message: req.t("common:field.BASE_TYPE_CHOICES", {
+ types: Array.from(ConnectionStore.connections.keys()).join(", "),
+ }),
+ },
+ });
- if (!connection.settings.enabled)
- throw FieldErrors({
- provider_id: {
- message: "This connection has been disabled server-side.",
- },
- });
+ if (!connection.settings.enabled)
+ throw FieldErrors({
+ provider_id: {
+ message: "This connection has been disabled server-side.",
+ },
+ });
- const body = req.body as ConnectionCallbackSchema;
- const userId = connection.getUserId(body.state);
- const connectedAccnt = await connection.handleCallback(body);
+ const body = req.body as ConnectionCallbackSchema;
+ const userId = connection.getUserId(body.state);
+ const connectedAccnt = await connection.handleCallback(body);
- // whether we should emit a connections update event, only used when a connection doesnt already exist
- if (connectedAccnt)
- emitEvent({
- event: "USER_CONNECTIONS_UPDATE",
- data: { ...connectedAccnt, token_data: undefined },
- user_id: userId,
- });
+ // whether we should emit a connections update event, only used when a connection doesnt already exist
+ if (connectedAccnt)
+ emitEvent({
+ event: "USER_CONNECTIONS_UPDATE",
+ data: { ...connectedAccnt, token_data: undefined },
+ user_id: userId,
+ });
- res.sendStatus(204);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/connections/index.ts b/src/api/routes/connections/index.ts
index b30d40276..017ebbbb5 100644
--- a/src/api/routes/connections/index.ts
+++ b/src/api/routes/connections/index.ts
@@ -22,24 +22,24 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIConnectionsConfiguration",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const config = ConnectionConfig.get();
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIConnectionsConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const config = ConnectionConfig.get();
- Object.keys(config).forEach((key) => {
- delete config[key].clientId;
- delete config[key].clientSecret;
- });
+ Object.keys(config).forEach((key) => {
+ delete config[key].clientId;
+ delete config[key].clientSecret;
+ });
- res.json(config);
- },
+ res.json(config);
+ },
);
export default router;
diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts
index ce34aa04f..dcecec389 100644
--- a/src/api/routes/discoverable-guilds.ts
+++ b/src/api/routes/discoverable-guilds.ts
@@ -25,52 +25,52 @@ import { Like } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "DiscoverableGuildsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { offset, limit, categories } = req.query;
- const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
- const configLimit = Config.get().guild.discovery.limit;
- let guilds;
- if (categories == undefined) {
- guilds = showAllGuilds
- ? await Guild.find({
- take: Math.abs(Number(limit || configLimit)),
- })
- : await Guild.find({
- where: { features: Like(`%DISCOVERABLE%`) },
- take: Math.abs(Number(limit || configLimit)),
- });
- } else {
- guilds = showAllGuilds
- ? await Guild.find({
- where: { primary_category_id: categories.toString() },
- take: Math.abs(Number(limit || configLimit)),
- })
- : await Guild.find({
- where: {
- primary_category_id: categories.toString(),
- features: Like("%DISCOVERABLE%"),
- },
- take: Math.abs(Number(limit || configLimit)),
- });
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "DiscoverableGuildsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { offset, limit, categories } = req.query;
+ const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
+ const configLimit = Config.get().guild.discovery.limit;
+ let guilds;
+ if (categories == undefined) {
+ guilds = showAllGuilds
+ ? await Guild.find({
+ take: Math.abs(Number(limit || configLimit)),
+ })
+ : await Guild.find({
+ where: { features: Like(`%DISCOVERABLE%`) },
+ take: Math.abs(Number(limit || configLimit)),
+ });
+ } else {
+ guilds = showAllGuilds
+ ? await Guild.find({
+ where: { primary_category_id: categories.toString() },
+ take: Math.abs(Number(limit || configLimit)),
+ })
+ : await Guild.find({
+ where: {
+ primary_category_id: categories.toString(),
+ features: Like("%DISCOVERABLE%"),
+ },
+ take: Math.abs(Number(limit || configLimit)),
+ });
+ }
- const total = guilds ? guilds.length : undefined;
+ const total = guilds ? guilds.length : undefined;
- res.send({
- total: total,
- guilds: guilds,
- offset: Number(offset || Config.get().guild.discovery.offset),
- limit: Number(limit || configLimit),
- });
- },
+ res.send({
+ total: total,
+ guilds: guilds,
+ offset: Number(offset || Config.get().guild.discovery.offset),
+ limit: Number(limit || configLimit),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts
index cb1e3a9e0..d865dfe88 100644
--- a/src/api/routes/discovery.ts
+++ b/src/api/routes/discovery.ts
@@ -23,25 +23,25 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/categories",
- route({
- responses: {
- 200: {
- body: "APIDiscoveryCategoryArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO:
- // Get locale instead
+ "/categories",
+ route({
+ responses: {
+ 200: {
+ body: "APIDiscoveryCategoryArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO:
+ // Get locale instead
- // const { locale, primary_only } = req.query;
- const { primary_only } = req.query;
+ // const { locale, primary_only } = req.query;
+ const { primary_only } = req.query;
- const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find();
+ const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find();
- res.send(out);
- },
+ res.send(out);
+ },
);
export default router;
diff --git a/src/api/routes/download.ts b/src/api/routes/download.ts
index 06588326a..f54a3bb28 100644
--- a/src/api/routes/download.ts
+++ b/src/api/routes/download.ts
@@ -23,36 +23,36 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 302: {},
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { platform } = req.query;
+ "/",
+ route({
+ responses: {
+ 302: {},
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { platform } = req.query;
- if (!platform)
- throw FieldErrors({
- platform: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
+ if (!platform)
+ throw FieldErrors({
+ platform: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
- const release = await ClientRelease.findOneOrFail({
- where: {
- enabled: true,
- platform: platform as string,
- },
- order: { pub_date: "DESC" },
- });
+ const release = await ClientRelease.findOneOrFail({
+ where: {
+ enabled: true,
+ platform: platform as string,
+ },
+ order: { pub_date: "DESC" },
+ });
- res.redirect(release.url);
- },
+ res.redirect(release.url);
+ },
);
export default router;
diff --git a/src/api/routes/emojis/#emoji_id/source.ts b/src/api/routes/emojis/#emoji_id/source.ts
index 7a5e1c7af..f5c4a8308 100644
--- a/src/api/routes/emojis/#emoji_id/source.ts
+++ b/src/api/routes/emojis/#emoji_id/source.ts
@@ -24,63 +24,63 @@ import { APIErrorResponse, EmojiGuild, EmojiSourceResponse } from "@spacebar/sch
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "EmojiSourceResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { emoji_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "EmojiSourceResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { emoji_id } = req.params;
- const emoji = await Emoji.findOne({ where: { id: emoji_id } });
- if (!emoji) {
- res.status(404).json({
- code: DiscordApiErrors.UNKNOWN_EMOJI.code,
- message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`,
- errors: {},
- } as APIErrorResponse);
- return;
- }
+ const emoji = await Emoji.findOne({ where: { id: emoji_id } });
+ if (!emoji) {
+ res.status(404).json({
+ code: DiscordApiErrors.UNKNOWN_EMOJI.code,
+ message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`,
+ errors: {},
+ } as APIErrorResponse);
+ return;
+ }
- // TODO: emojis can be owned by applications these days, account for this when we get there?
- res.json({
- type: "GUILD",
- guild: {
- ...(await Guild.findOne({
- where: {
- id: emoji.guild_id,
- },
- select: {
- id: true,
- name: true,
- icon: true,
- description: true,
- features: true,
- emojis: true,
- premium_tier: true,
- premium_subscription_count: true,
- },
- })),
- approximate_member_count: await Member.countBy({
- guild_id: emoji.guild_id,
- }),
- approximate_presence_count: await Member.countBy({
- guild_id: emoji.guild_id,
- user: {
- sessions: {
- status: "online",
- },
- },
- }),
- } as EmojiGuild,
- } as EmojiSourceResponse);
- },
+ // TODO: emojis can be owned by applications these days, account for this when we get there?
+ res.json({
+ type: "GUILD",
+ guild: {
+ ...(await Guild.findOne({
+ where: {
+ id: emoji.guild_id,
+ },
+ select: {
+ id: true,
+ name: true,
+ icon: true,
+ description: true,
+ features: true,
+ emojis: true,
+ premium_tier: true,
+ premium_subscription_count: true,
+ },
+ })),
+ approximate_member_count: await Member.countBy({
+ guild_id: emoji.guild_id,
+ }),
+ approximate_presence_count: await Member.countBy({
+ guild_id: emoji.guild_id,
+ user: {
+ sessions: {
+ status: "online",
+ },
+ },
+ }),
+ } as EmojiGuild,
+ } as EmojiSourceResponse);
+ },
);
export default router;
diff --git a/src/api/routes/experiments.ts b/src/api/routes/experiments.ts
index bf120639a..9dd54cc0f 100644
--- a/src/api/routes/experiments.ts
+++ b/src/api/routes/experiments.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.send({ fingerprint: "", assignments: [], guild_experiments: [] });
+ // TODO:
+ res.send({ fingerprint: "", assignments: [], guild_experiments: [] });
});
export default router;
diff --git a/src/api/routes/games/detectable.ts b/src/api/routes/games/detectable.ts
index 9e10bd583..e1415ea33 100644
--- a/src/api/routes/games/detectable.ts
+++ b/src/api/routes/games/detectable.ts
@@ -22,33 +22,33 @@ import { ApplicationDetectableResponse } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
const cache = {
- data: {},
- expires: 0,
+ data: {},
+ expires: 0,
};
// modern dclients call this, is /applications/detectable deprecated?
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationDetectableResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // cache for 6 hours
- if (Date.now() > cache.expires) {
- const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
- const data = await response.json();
- cache.data = data as ApplicationDetectableResponse;
- cache.expires = Date.now() + 6 * 60 * 60 * 1000;
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationDetectableResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // cache for 6 hours
+ if (Date.now() > cache.expires) {
+ const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
+ const data = await response.json();
+ cache.data = data as ApplicationDetectableResponse;
+ cache.expires = Date.now() + 6 * 60 * 60 * 1000;
+ }
- res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
- .status(200)
- .json(cache.data);
- },
+ res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
+ .status(200)
+ .json(cache.data);
+ },
);
export default router;
diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index 80fa95124..34a648cda 100644
--- a/src/api/routes/gateway/bot.ts
+++ b/src/api/routes/gateway/bot.ts
@@ -23,27 +23,27 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GatewayBotResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- const { endpointPublic } = Config.get().gateway;
- res.json({
- url: endpointPublic,
- shards: 1,
- session_start_limit: {
- total: 1000,
- remaining: 999,
- reset_after: 14400000,
- max_concurrency: 1,
- },
- });
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GatewayBotResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { endpointPublic } = Config.get().gateway;
+ res.json({
+ url: endpointPublic,
+ shards: 1,
+ session_start_limit: {
+ total: 1000,
+ remaining: 999,
+ reset_after: 14400000,
+ max_concurrency: 1,
+ },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 1fafbf5bc..b85fcd9f6 100644
--- a/src/api/routes/gateway/index.ts
+++ b/src/api/routes/gateway/index.ts
@@ -23,20 +23,20 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GatewayResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- const { endpointPublic } = Config.get().gateway;
- res.json({
- url: endpointPublic,
- });
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GatewayResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { endpointPublic } = Config.get().gateway;
+ res.json({
+ url: endpointPublic,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index e81c2c3a8..ca07956ef 100644
--- a/src/api/routes/gifs/search.ts
+++ b/src/api/routes/gifs/search.ts
@@ -25,45 +25,45 @@ import { TenorGif, TenorMediaTypes } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- q: {
- type: "string",
- required: true,
- description: "Search query",
- },
- media_format: {
- type: "string",
- description: "Media format",
- values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
- },
- locale: {
- type: "string",
- description: "Locale",
- },
- },
- responses: {
- 200: {
- body: "TenorGifsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Custom providers
- const { q, media_format, locale } = req.query;
+ "/",
+ route({
+ query: {
+ q: {
+ type: "string",
+ required: true,
+ description: "Search query",
+ },
+ media_format: {
+ type: "string",
+ description: "Media format",
+ values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
+ },
+ locale: {
+ type: "string",
+ description: "Locale",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TenorGifsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ const { q, media_format, locale } = req.query;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- });
+ const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
- const { results } = (await response.json()) as { results: TenorGif[] };
+ const { results } = (await response.json()) as { results: TenorGif[] };
- res.json(results.map(parseGifResult)).status(200);
- },
+ res.json(results.map(parseGifResult)).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index d5907b4af..99aa528b6 100644
--- a/src/api/routes/gifs/trending-gifs.ts
+++ b/src/api/routes/gifs/trending-gifs.ts
@@ -24,40 +24,40 @@ import { TenorGif, TenorMediaTypes } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- media_format: {
- type: "string",
- description: "Media format",
- values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
- },
- locale: {
- type: "string",
- description: "Locale",
- },
- },
- responses: {
- 200: {
- body: "TenorGifsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Custom providers
- const { media_format, locale } = req.query;
+ "/",
+ route({
+ query: {
+ media_format: {
+ type: "string",
+ description: "Media format",
+ values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
+ },
+ locale: {
+ type: "string",
+ description: "Locale",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TenorGifsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ const { media_format, locale } = req.query;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- });
+ const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
- const { results } = (await response.json()) as { results: TenorGif[] };
+ const { results } = (await response.json()) as { results: TenorGif[] };
- res.json(results.map(parseGifResult)).status(200);
- },
+ res.json(results.map(parseGifResult)).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index d86c06b5b..4a7a3f3ce 100644
--- a/src/api/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -24,50 +24,50 @@ import { TenorCategoriesResults, TenorTrendingResults } from "@spacebar/schemas"
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- locale: {
- type: "string",
- description: "Locale",
- },
- },
- responses: {
- 200: {
- body: "TenorTrendingResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Custom providers
- // TODO: return gifs as mp4
- // const { media_format, locale } = req.query;
- const { locale } = req.query;
+ "/",
+ route({
+ query: {
+ locale: {
+ type: "string",
+ description: "Locale",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TenorTrendingResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ // TODO: return gifs as mp4
+ // const { media_format, locale } = req.query;
+ const { locale } = req.query;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const [responseSource, trendGifSource] = await Promise.all([
- fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- }),
- fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- }),
- ]);
+ const [responseSource, trendGifSource] = await Promise.all([
+ fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ }),
+ fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ }),
+ ]);
- const { tags } = (await responseSource.json()) as TenorCategoriesResults;
- const { results } = (await trendGifSource.json()) as TenorTrendingResults;
+ const { tags } = (await responseSource.json()) as TenorCategoriesResults;
+ const { results } = (await trendGifSource.json()) as TenorTrendingResults;
- res.json({
- categories: tags.map((x) => ({
- name: x.searchterm,
- src: x.image,
- })),
- gifs: [parseGifResult(results[0])],
- }).status(200);
- },
+ res.json({
+ categories: tags.map((x) => ({
+ name: x.searchterm,
+ src: x.image,
+ })),
+ gifs: [parseGifResult(results[0])],
+ }).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index fa642ea4f..38ba10257 100644
--- a/src/api/routes/guild-recommendations.ts
+++ b/src/api/routes/guild-recommendations.ts
@@ -25,32 +25,32 @@ import { Like } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildRecommendationsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { limit, personalization_disabled } = req.query;
- const { limit } = req.query;
- const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildRecommendationsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { limit, personalization_disabled } = req.query;
+ const { limit } = req.query;
+ const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
- const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
+ const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
- const guilds = showAllGuilds
- ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
- : await Guild.find({
- where: { features: Like("%DISCOVERABLE%") },
- take: Math.abs(Number(limit || 24)),
- });
- res.send({
- recommended_guilds: guilds,
- load_id: `server_recs/${genLoadId(32)}`,
- }).status(200);
- },
+ const guilds = showAllGuilds
+ ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
+ : await Guild.find({
+ where: { features: Like("%DISCOVERABLE%") },
+ take: Math.abs(Number(limit || 24)),
+ });
+ res.send({
+ recommended_guilds: guilds,
+ load_id: `server_recs/${genLoadId(32)}`,
+ }).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/application-command-index.ts b/src/api/routes/guilds/#guild_id/application-command-index.ts
index bb1d80000..244029d58 100644
--- a/src/api/routes/guilds/#guild_id/application-command-index.ts
+++ b/src/api/routes/guilds/#guild_id/application-command-index.ts
@@ -25,66 +25,66 @@ import { ApplicationCommandSchema, ApplicationCommandType } from "@spacebar/sche
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } });
- const applications: Application[] = [];
+ const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } });
+ const applications: Application[] = [];
- for (const member of members) {
- const app = await Application.findOne({ where: { id: member.id } });
- if (app) applications.push(app);
- }
+ for (const member of members) {
+ const app = await Application.findOne({ where: { id: member.id } });
+ if (app) applications.push(app);
+ }
- const applicationsSendable = [];
+ const applicationsSendable = [];
- for (const application of applications) {
- applicationsSendable.push({
- bot_id: application.bot?.id,
- description: application.description,
- flags: application.flags,
- icon: application.icon,
- id: application.id,
- name: application.name,
- });
- }
+ for (const application of applications) {
+ applicationsSendable.push({
+ bot_id: application.bot?.id,
+ description: application.description,
+ flags: application.flags,
+ icon: application.icon,
+ id: application.id,
+ name: application.name,
+ });
+ }
- const applicationCommands: ApplicationCommand[][] = [];
+ const applicationCommands: ApplicationCommand[][] = [];
- for (const application of applications) {
- applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } }));
- applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } }));
- }
+ for (const application of applications) {
+ applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } }));
+ applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } }));
+ }
- const applicationCommandsSendable: ApplicationCommandSchema[] = [];
+ const applicationCommandsSendable: ApplicationCommandSchema[] = [];
- for (const command of applicationCommands.flat()) {
- applicationCommandsSendable.push({
- id: command.id,
- type: command.type,
- application_id: command.application_id,
- guild_id: command.guild_id,
- name: command.name,
- name_localizations: command.name_localizations,
- // name_localized: // TODO: make this work
- description: command.description,
- description_localizations: command.description_localizations,
- // description_localized: // TODO: make this work
- options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined,
- default_member_permissions: command.default_member_permissions,
- dm_permission: command.dm_permission,
- permissions: command.permissions,
- nsfw: command.nsfw,
- integration_types: command.integration_types,
- global_popularity_rank: command.global_popularity_rank,
- contexts: command.contexts,
- version: command.version,
- handler: command.handler,
- });
- }
+ for (const command of applicationCommands.flat()) {
+ applicationCommandsSendable.push({
+ id: command.id,
+ type: command.type,
+ application_id: command.application_id,
+ guild_id: command.guild_id,
+ name: command.name,
+ name_localizations: command.name_localizations,
+ // name_localized: // TODO: make this work
+ description: command.description,
+ description_localizations: command.description_localizations,
+ // description_localized: // TODO: make this work
+ options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined,
+ default_member_permissions: command.default_member_permissions,
+ dm_permission: command.dm_permission,
+ permissions: command.permissions,
+ nsfw: command.nsfw,
+ integration_types: command.integration_types,
+ global_popularity_rank: command.global_popularity_rank,
+ contexts: command.contexts,
+ version: command.version,
+ handler: command.handler,
+ });
+ }
- res.send({
- applications: applicationsSendable,
- application_commands: applicationCommandsSendable,
- version: Snowflake.generate(),
- });
+ res.send({
+ applications: applicationsSendable,
+ application_commands: applicationCommandsSendable,
+ version: Snowflake.generate(),
+ });
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index 620f88228..4acc8e50f 100644
--- a/src/api/routes/guilds/#guild_id/audit-logs.ts
+++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -22,14 +22,14 @@ const router = Router({ mergeParams: true });
//TODO: implement audit logs
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json({
- audit_log_entries: [],
- users: [],
- integrations: [],
- webhooks: [],
- guild_scheduled_events: [],
- threads: [],
- application_commands: [],
- });
+ res.json({
+ audit_log_entries: [],
+ users: [],
+ integrations: [],
+ webhooks: [],
+ guild_scheduled_events: [],
+ threads: [],
+ application_commands: [],
+ });
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
index e2b373247..8c24651d2 100644
--- a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
+++ b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
@@ -25,116 +25,116 @@ import { AutomodRuleSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- permission: ["MANAGE_GUILD"],
- responses: {
- 200: {
- body: "AutomodRuleSchemaWithId[]",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const rules = await AutomodRule.find({ where: { guild_id } });
- return res.json(rules);
- },
+ "/",
+ route({
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId[]",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const rules = await AutomodRule.find({ where: { guild_id } });
+ return res.json(rules);
+ },
);
router.post(
- "/",
- route({
- // requestBody: "AutomodRuleSchema",
- permission: ["MANAGE_GUILD"],
- responses: {
- 200: {
- body: "AutomodRuleSchemaWithId",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403);
+ "/",
+ route({
+ // requestBody: "AutomodRuleSchema",
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403);
- if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403);
+ if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403);
- if (req.body.id) {
- throw new HTTPError("You can't specify an ID for a new rule", 400);
- }
+ if (req.body.id) {
+ throw new HTTPError("You can't specify an ID for a new rule", 400);
+ }
- const data = req.body as AutomodRuleSchema;
+ const data = req.body as AutomodRuleSchema;
- const created = AutomodRule.create({
- creator: await User.findOneOrFail({
- where: { id: data.creator_id },
- }),
- ...data,
- });
+ const created = AutomodRule.create({
+ creator: await User.findOneOrFail({
+ where: { id: data.creator_id },
+ }),
+ ...data,
+ });
- const savedRule = await AutomodRule.save(created);
- return res.json(savedRule);
- },
+ const savedRule = await AutomodRule.save(created);
+ return res.json(savedRule);
+ },
);
router.patch(
- "/:rule_id",
- route({
- // requestBody: "AutomodRuleSchema
- permission: ["MANAGE_GUILD"],
- responses: {
- 200: {
- body: "AutomodRuleSchemaWithId",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { rule_id } = req.params;
- const rule = await AutomodRule.findOneOrFail({
- where: { id: rule_id },
- });
+ "/:rule_id",
+ route({
+ // requestBody: "AutomodRuleSchema
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { rule_id } = req.params;
+ const rule = await AutomodRule.findOneOrFail({
+ where: { id: rule_id },
+ });
- const data = req.body as AutomodRuleSchema;
+ const data = req.body as AutomodRuleSchema;
- AutomodRule.merge(rule, data);
- const savedRule = await AutomodRule.save(rule);
- return res.json(savedRule);
- },
+ AutomodRule.merge(rule, data);
+ const savedRule = await AutomodRule.save(rule);
+ return res.json(savedRule);
+ },
);
router.delete(
- "/:rule_id",
- route({
- permission: ["MANAGE_GUILD"],
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { rule_id } = req.params;
- await AutomodRule.delete({ id: rule_id });
- return res.status(204).send();
- },
+ "/:rule_id",
+ route({
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { rule_id } = req.params;
+ await AutomodRule.delete({ id: rule_id });
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index 03b4edea0..cf043b7e2 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -27,267 +27,267 @@ const router: Router = Router({ mergeParams: true });
/* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */
router.get(
- "/",
- route({
- permission: "BAN_MEMBERS",
- responses: {
- 200: {
- body: "APIBansArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ permission: "BAN_MEMBERS",
+ responses: {
+ 200: {
+ body: "APIBansArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- let bans = await Ban.find({ where: { guild_id: guild_id } });
- const promisesToAwait: Promise[] = [];
- const bansObj: APIBansArray = [];
+ let bans = await Ban.find({ where: { guild_id: guild_id } });
+ const promisesToAwait: Promise[] = [];
+ const bansObj: APIBansArray = [];
- bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
+ bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
- bans.forEach((ban) => {
- promisesToAwait.push(User.getPublicUser(ban.user_id));
- });
+ bans.forEach((ban) => {
+ promisesToAwait.push(User.getPublicUser(ban.user_id));
+ });
- const bannedUsers = await Promise.all(promisesToAwait);
+ const bannedUsers = await Promise.all(promisesToAwait);
- bans.forEach((ban, index) => {
- const user = bannedUsers[index];
- bansObj.push({
- reason: ban.reason ?? null,
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar ?? null,
- public_flags: user.public_flags,
- },
- });
- });
+ bans.forEach((ban, index) => {
+ const user = bannedUsers[index];
+ bansObj.push({
+ reason: ban.reason ?? null,
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar ?? null,
+ public_flags: user.public_flags,
+ },
+ });
+ });
- return res.json(bansObj);
- },
+ return res.json(bansObj);
+ },
);
router.get(
- "/search",
- route({
- permission: "BAN_MEMBERS",
- query: {
- query: {
- type: "string",
- description: "Query to match username(s) and display name(s) against (1-32 characters)",
- required: true,
- },
- limit: {
- type: "number",
- description: "Max number of members to return (1-10, default 10)",
- required: false,
- },
- },
- responses: {
- 200: {
- body: "APIBansArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/search",
+ route({
+ permission: "BAN_MEMBERS",
+ query: {
+ query: {
+ type: "string",
+ description: "Query to match username(s) and display name(s) against (1-32 characters)",
+ required: true,
+ },
+ limit: {
+ type: "number",
+ description: "Max number of members to return (1-10, default 10)",
+ required: false,
+ },
+ },
+ responses: {
+ 200: {
+ body: "APIBansArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const limit = Number(req.query.limit) || 10;
- if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10");
+ const limit = Number(req.query.limit) || 10;
+ if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10");
- const query = String(req.query.query);
- if (!query || query.trim().length === 0 || query.length > 32) {
- throw new HTTPError("The query must be between 1 and 32 characters in length");
- }
+ const query = String(req.query.query);
+ if (!query || query.trim().length === 0 || query.length > 32) {
+ throw new HTTPError("The query must be between 1 and 32 characters in length");
+ }
- let bans = await Ban.createQueryBuilder("ban")
- .leftJoinAndSelect("ban.user", "user")
- .where("ban.guild_id = :guildId", { guildId: guild_id })
- .andWhere("user.username LIKE :userName", {
- userName: `%${query}%`,
- })
- .limit(limit)
- .getMany();
+ let bans = await Ban.createQueryBuilder("ban")
+ .leftJoinAndSelect("ban.user", "user")
+ .where("ban.guild_id = :guildId", { guildId: guild_id })
+ .andWhere("user.username LIKE :userName", {
+ userName: `%${query}%`,
+ })
+ .limit(limit)
+ .getMany();
- bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
+ bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
- const bansObj: APIBansArray = bans.map((ban) => {
- const user = ban.user;
- return {
- reason: ban.reason ?? null,
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar ?? null,
- public_flags: user.public_flags,
- },
- };
- });
+ const bansObj: APIBansArray = bans.map((ban) => {
+ const user = ban.user;
+ return {
+ reason: ban.reason ?? null,
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar ?? null,
+ public_flags: user.public_flags,
+ },
+ };
+ });
- return res.json(bansObj);
- },
+ return res.json(bansObj);
+ },
);
router.get(
- "/:user_id",
- route({
- permission: "BAN_MEMBERS",
- responses: {
- 200: {
- body: "GuildBansResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, user_id } = req.params;
+ "/:user_id",
+ route({
+ permission: "BAN_MEMBERS",
+ responses: {
+ 200: {
+ body: "GuildBansResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, user_id } = req.params;
- const ban = (await Ban.findOneOrFail({
- where: { guild_id: guild_id, user_id: user_id },
- })) as BanRegistrySchema;
+ const ban = (await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ })) as BanRegistrySchema;
- if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
- // pretend self-bans don't exist to prevent victim chasing
+ if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
+ // pretend self-bans don't exist to prevent victim chasing
- const user = await User.getPublicUser(ban.user_id);
+ const user = await User.getPublicUser(ban.user_id);
- const banInfo: GuildBansResponse = {
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar ?? null,
- public_flags: user.public_flags,
- },
- reason: ban.reason ?? null,
- };
+ const banInfo: GuildBansResponse = {
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar ?? null,
+ public_flags: user.public_flags,
+ },
+ reason: ban.reason ?? null,
+ };
- return res.json(banInfo);
- },
+ return res.json(banInfo);
+ },
);
router.put(
- "/:user_id",
- route({
- requestBody: "BanCreateSchema",
- permission: "BAN_MEMBERS",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const banned_user_id = req.params.user_id;
- const opts = req.body as BanCreateSchema;
+ "/:user_id",
+ route({
+ requestBody: "BanCreateSchema",
+ permission: "BAN_MEMBERS",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const banned_user_id = req.params.user_id;
+ const opts = req.body as BanCreateSchema;
- let deleteMessagesMs = opts.delete_message_days
- ? (opts.delete_message_days as number) * 86400000
- : opts.delete_message_seconds
- ? (opts.delete_message_seconds as number) * 1000
- : 0;
+ let deleteMessagesMs = opts.delete_message_days
+ ? (opts.delete_message_days as number) * 86400000
+ : opts.delete_message_seconds
+ ? (opts.delete_message_seconds as number) * 1000
+ : 0;
- if (deleteMessagesMs < 0) deleteMessagesMs = 0;
+ if (deleteMessagesMs < 0) deleteMessagesMs = 0;
- if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id)
- throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
+ if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id)
+ throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
- 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 existingBan = await Ban.findOne({
- where: { guild_id: guild_id, user_id: banned_user_id },
- });
+ const existingBan = await Ban.findOne({
+ where: { guild_id: guild_id, user_id: banned_user_id },
+ });
- // Bans on already banned users are silently ignored
- if (existingBan) return res.status(204).send();
+ // Bans on already banned users are silently ignored
+ if (existingBan) return res.status(204).send();
- const banned_user = await User.getPublicUser(banned_user_id);
+ const banned_user = await User.getPublicUser(banned_user_id);
- const ban = Ban.create({
- user_id: banned_user_id,
- guild_id: guild_id,
- executor_id: req.user_id,
- reason: req.body.reason, // || otherwise empty
- });
+ const ban = Ban.create({
+ user_id: banned_user_id,
+ guild_id: guild_id,
+ executor_id: req.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- await Promise.all([
- Member.removeFromGuild(banned_user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
- guild_id: guild_id,
- user: banned_user.toPublicUser(),
- delete_message_secs: Math.floor(deleteMessagesMs / 1000),
- },
- guild_id: guild_id,
- } as GuildBanAddEvent),
- ]);
+ await Promise.all([
+ Member.removeFromGuild(banned_user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user.toPublicUser(),
+ delete_message_secs: Math.floor(deleteMessagesMs / 1000),
+ },
+ guild_id: guild_id,
+ } as GuildBanAddEvent),
+ ]);
- return res.status(204).send();
- },
+ return res.status(204).send();
+ },
);
router.delete(
- "/:user_id",
- route({
- permission: "BAN_MEMBERS",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, user_id } = req.params;
+ "/:user_id",
+ route({
+ permission: "BAN_MEMBERS",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, user_id } = req.params;
- await Ban.findOneOrFail({
- where: { guild_id: guild_id, user_id: user_id },
- });
+ await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ });
- const banned_user = await User.getPublicUser(user_id);
+ const banned_user = await User.getPublicUser(user_id);
- await Promise.all([
- Ban.delete({
- user_id: user_id,
- guild_id,
- }),
+ await Promise.all([
+ Ban.delete({
+ user_id: user_id,
+ guild_id,
+ }),
- emitEvent({
- event: "GUILD_BAN_REMOVE",
- data: {
- guild_id,
- user: banned_user,
- },
- guild_id,
- } as GuildBanRemoveEvent),
- ]);
+ emitEvent({
+ event: "GUILD_BAN_REMOVE",
+ data: {
+ guild_id,
+ user: banned_user,
+ },
+ guild_id,
+ } as GuildBanRemoveEvent),
+ ]);
- return res.status(204).send();
- },
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bulk-ban.ts b/src/api/routes/guilds/#guild_id/bulk-ban.ts
index a564e2d48..004c2ab26 100644
--- a/src/api/routes/guilds/#guild_id/bulk-ban.ts
+++ b/src/api/routes/guilds/#guild_id/bulk-ban.ts
@@ -25,93 +25,93 @@ import { Config } from "@spacebar/util";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "BulkBanSchema",
- permission: ["BAN_MEMBERS", "MANAGE_GUILD"],
- responses: {
- 200: {
- body: "Ban",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ requestBody: "BulkBanSchema",
+ permission: ["BAN_MEMBERS", "MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "Ban",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const userIds: Array = req.body.user_ids;
- if (!userIds) throw new HTTPError("The user_ids array is missing", 400);
+ const userIds: Array = req.body.user_ids;
+ if (!userIds) throw new HTTPError("The user_ids array is missing", 400);
- if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400);
+ if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400);
- const banned_users = [];
- const failed_users = [];
- for await (const banned_user_id of userIds) {
- if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) {
- failed_users.push(banned_user_id);
- continue;
- }
+ const banned_users = [];
+ const failed_users = [];
+ for await (const banned_user_id of userIds) {
+ if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- if (req.permission?.cache.guild?.owner_id === banned_user_id) {
- failed_users.push(banned_user_id);
- continue;
- }
+ if (req.permission?.cache.guild?.owner_id === banned_user_id) {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- const existingBan = await Ban.findOne({
- where: { guild_id: guild_id, user_id: banned_user_id },
- });
- if (existingBan) {
- failed_users.push(banned_user_id);
- continue;
- }
+ const existingBan = await Ban.findOne({
+ where: { guild_id: guild_id, user_id: banned_user_id },
+ });
+ if (existingBan) {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- let banned_user;
- try {
- banned_user = await User.getPublicUser(banned_user_id);
- } catch {
- failed_users.push(banned_user_id);
- continue;
- }
+ let banned_user;
+ try {
+ banned_user = await User.getPublicUser(banned_user_id);
+ } catch {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- const ban = Ban.create({
- user_id: banned_user_id,
- guild_id: guild_id,
- ip: req.ip,
- executor_id: req.user_id,
- reason: req.body.reason, // || otherwise empty
- });
+ const ban = Ban.create({
+ user_id: banned_user_id,
+ guild_id: guild_id,
+ ip: req.ip,
+ executor_id: req.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- try {
- await Promise.all([
- Member.removeFromGuild(banned_user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
- guild_id: guild_id,
- user: banned_user.toPublicUser(),
- },
- guild_id: guild_id,
- } as GuildBanAddEvent),
- ]);
- banned_users.push(banned_user_id);
- } catch {
- failed_users.push(banned_user_id);
- continue;
- }
- }
+ try {
+ await Promise.all([
+ Member.removeFromGuild(banned_user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user.toPublicUser(),
+ },
+ guild_id: guild_id,
+ } as GuildBanAddEvent),
+ ]);
+ banned_users.push(banned_user_id);
+ } catch {
+ failed_users.push(banned_user_id);
+ continue;
+ }
+ }
- if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED;
- return res.json({
- banned_users: banned_users,
- failed_users: failed_users,
- });
- },
+ if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED;
+ return res.json({
+ banned_users: banned_users,
+ failed_users: failed_users,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index ecb25beee..fc374acb3 100644
--- a/src/api/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -23,148 +23,148 @@ import { ChannelModifySchema, ChannelReorderSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 201: {
- body: "APIChannelArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const channels = await Channel.find({ where: { guild_id } });
+ "/",
+ route({
+ responses: {
+ 201: {
+ body: "APIChannelArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const channels = await Channel.find({ where: { guild_id } });
- for await (const channel of channels) {
- channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
- }
- channels.sort((a, b) => a.position - b.position);
+ for await (const channel of channels) {
+ channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
+ }
+ channels.sort((a, b) => a.position - b.position);
- res.json(channels);
- },
+ res.json(channels);
+ },
);
router.post(
- "/",
- route({
- requestBody: "ChannelModifySchema",
- permission: "MANAGE_CHANNELS",
- responses: {
- 201: {
- body: "Channel",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- 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;
+ "/",
+ route({
+ requestBody: "ChannelModifySchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 201: {
+ body: "Channel",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ 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;
- const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
- channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
+ const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
+ channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
- res.status(201).json(channel);
- },
+ res.status(201).json(channel);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ChannelReorderSchema",
- permission: "MANAGE_CHANNELS",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // changes guild channel position
- const { guild_id } = req.params;
- let body = req.body as ChannelReorderSchema;
+ "/",
+ route({
+ requestBody: "ChannelReorderSchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // changes guild channel position
+ const { guild_id } = req.params;
+ let body = req.body as ChannelReorderSchema;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: { channel_ordering: true },
- });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: { channel_ordering: true },
+ });
- body = body.sort((a, b) => {
- const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0);
- const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0);
- return apos - bpos;
- });
+ body = body.sort((a, b) => {
+ const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0);
+ const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0);
+ return apos - bpos;
+ });
- // The channels not listed for this query
- const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x));
+ // The channels not listed for this query
+ const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x));
- const withParents = body.filter((x) => x.parent_id !== undefined);
- const withPositions = body.filter((x) => x.position !== undefined);
- // You can't do it with Promise.all or the way this is being done is super incorrect
- for await (const opt of withPositions) {
- const channel = await Channel.findOneOrFail({
- where: { id: opt.id },
- });
+ const withParents = body.filter((x) => x.parent_id !== undefined);
+ const withPositions = body.filter((x) => x.position !== undefined);
+ // You can't do it with Promise.all or the way this is being done is super incorrect
+ for await (const opt of withPositions) {
+ const channel = await Channel.findOneOrFail({
+ where: { id: opt.id },
+ });
- notMentioned.splice(opt.position as number, 0, channel.id);
- channel.position = notMentioned.findIndex((_) => _ === channel.id);
+ notMentioned.splice(opt.position as number, 0, channel.id);
+ channel.position = notMentioned.findIndex((_) => _ === channel.id);
- await emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id: channel.id,
- guild_id,
- } as ChannelUpdateEvent);
- }
- // Due to this also being able to change the order, this needs to be done in order
- // have to do the parents after the positions
- for await (const opt of withParents) {
- const [channel, parent] = await Promise.all([
- Channel.findOneOrFail({
- where: { id: opt.id },
- }),
- opt.parent_id
- ? Channel.findOneOrFail({
- where: { id: opt.parent_id },
- select: {
- permission_overwrites: true,
- id: true,
- },
- })
- : null,
- ]);
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id: channel.id,
+ guild_id,
+ } as ChannelUpdateEvent);
+ }
+ // Due to this also being able to change the order, this needs to be done in order
+ // have to do the parents after the positions
+ for await (const opt of withParents) {
+ const [channel, parent] = await Promise.all([
+ Channel.findOneOrFail({
+ where: { id: opt.id },
+ }),
+ opt.parent_id
+ ? Channel.findOneOrFail({
+ where: { id: opt.parent_id },
+ select: {
+ permission_overwrites: true,
+ id: true,
+ },
+ })
+ : null,
+ ]);
- if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
- if (parent && opt.position === undefined) {
- const parentPos = notMentioned.indexOf(parent.id);
- notMentioned.splice(parentPos + 1, 0, channel.id);
- channel.position = (parentPos + 1) as number;
- }
- channel.parent = parent || undefined;
- channel.parent_id = parent?.id || null;
- await channel.save();
+ if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
+ if (parent && opt.position === undefined) {
+ const parentPos = notMentioned.indexOf(parent.id);
+ notMentioned.splice(parentPos + 1, 0, channel.id);
+ channel.position = (parentPos + 1) as number;
+ }
+ channel.parent = parent || undefined;
+ channel.parent_id = parent?.id || null;
+ await channel.save();
- await emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id: channel.id,
- guild_id,
- } as ChannelUpdateEvent);
- }
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id: channel.id,
+ guild_id,
+ } as ChannelUpdateEvent);
+ }
- await Guild.update({ id: guild_id }, { channel_ordering: notMentioned });
+ await Guild.update({ id: guild_id }, { channel_ordering: notMentioned });
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index 509445c4b..4edd2c39a 100644
--- a/src/api/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -26,40 +26,40 @@ const router = Router({ mergeParams: true });
// 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(
- "/",
- route({
- responses: {
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- 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);
+ 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);
- await Promise.all([
- Guild.delete({ id: guild_id }), // this will also delete all guild related data
- emitEvent({
- event: "GUILD_DELETE",
- data: {
- id: guild_id,
- },
- guild_id: guild_id,
- } as GuildDeleteEvent),
- ]);
+ await Promise.all([
+ Guild.delete({ id: guild_id }), // this will also delete all guild related data
+ emitEvent({
+ event: "GUILD_DELETE",
+ data: {
+ id: guild_id,
+ },
+ guild_id: guild_id,
+ } as GuildDeleteEvent),
+ ]);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index 89727335d..150c9209a 100644
--- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -22,44 +22,44 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildDiscoveryRequirementsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildDiscoveryRequirementsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
- res.send({
- guild_id: guild_id,
- safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0,
- },
- minimum_size: 0,
- });
- },
+ res.send({
+ guild_id: guild_id,
+ safe_environment: true,
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index c74f939fc..5b07380f6 100644
--- a/src/api/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -24,186 +24,186 @@ import { EmojiCreateSchema, EmojiModifySchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIEmojiArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIEmojiArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emojis = await Emoji.find({
- where: { guild_id: guild_id },
- relations: ["user"],
- });
+ const emojis = await Emoji.find({
+ where: { guild_id: guild_id },
+ relations: ["user"],
+ });
- return res.json(emojis);
- },
+ return res.json(emojis);
+ },
);
router.get(
- "/:emoji_id",
- route({
- responses: {
- 200: {
- body: "Emoji",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, emoji_id } = req.params;
+ "/:emoji_id",
+ route({
+ responses: {
+ 200: {
+ body: "Emoji",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, emoji_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emoji = await Emoji.findOneOrFail({
- where: { guild_id: guild_id, id: emoji_id },
- relations: ["user"],
- });
+ const emoji = await Emoji.findOneOrFail({
+ where: { guild_id: guild_id, id: emoji_id },
+ relations: ["user"],
+ });
- return res.json(emoji);
- },
+ return res.json(emoji);
+ },
);
router.post(
- "/",
- route({
- requestBody: "EmojiCreateSchema",
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 201: {
- body: "Emoji",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as EmojiCreateSchema;
+ "/",
+ route({
+ requestBody: "EmojiCreateSchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 201: {
+ body: "Emoji",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as EmojiCreateSchema;
- const id = Snowflake.generate();
- const emoji_count = await Emoji.count({
- where: { guild_id: guild_id },
- });
- const { maxEmojis } = Config.get().limits.guild;
+ const id = Snowflake.generate();
+ const emoji_count = await Emoji.count({
+ where: { guild_id: guild_id },
+ });
+ const { maxEmojis } = Config.get().limits.guild;
- if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
- if (body.require_colons == null) body.require_colons = true;
+ if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
+ if (body.require_colons == null) body.require_colons = true;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- await handleFile(`/emojis/${id}`, body.image);
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ await handleFile(`/emojis/${id}`, body.image);
- const mimeType = body.image.split(":")[1].split(";")[0];
- const emoji = await Emoji.create({
- id: id,
- guild_id: guild_id,
- name: body.name,
- require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
- user: user,
- managed: false,
- animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm",
- available: true,
- roles: [],
- }).save();
+ const mimeType = body.image.split(":")[1].split(";")[0];
+ const emoji = await Emoji.create({
+ id: id,
+ guild_id: guild_id,
+ name: body.name,
+ require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
+ user: user,
+ managed: false,
+ animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm",
+ available: true,
+ roles: [],
+ }).save();
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } }),
- },
- } as GuildEmojisUpdateEvent);
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- return res.status(201).json(emoji);
- },
+ return res.status(201).json(emoji);
+ },
);
router.patch(
- "/:emoji_id",
- route({
- requestBody: "EmojiModifySchema",
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 200: {
- body: "Emoji",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { emoji_id, guild_id } = req.params;
- const body = req.body as EmojiModifySchema;
+ "/:emoji_id",
+ route({
+ requestBody: "EmojiModifySchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 200: {
+ body: "Emoji",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { emoji_id, guild_id } = req.params;
+ const body = req.body as EmojiModifySchema;
- const emoji = await Emoji.create({
- ...body,
- id: emoji_id,
- guild_id: guild_id,
- }).save();
+ const emoji = await Emoji.create({
+ ...body,
+ id: emoji_id,
+ guild_id: guild_id,
+ }).save();
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } }),
- },
- } as GuildEmojisUpdateEvent);
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- return res.json(emoji);
- },
+ return res.json(emoji);
+ },
);
router.delete(
- "/:emoji_id",
- route({
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { emoji_id, guild_id } = req.params;
+ "/:emoji_id",
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { emoji_id, guild_id } = req.params;
- await Emoji.delete({
- id: emoji_id,
- guild_id: guild_id,
- });
+ await Emoji.delete({
+ id: emoji_id,
+ guild_id: guild_id,
+ });
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } }),
- },
- } as GuildEmojisUpdateEvent);
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index d6593bd8e..38cc91391 100644
--- a/src/api/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -25,188 +25,188 @@ import { GuildUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- "200": {
- body: "APIGuildWithJoinedAt",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ "200": {
+ body: "APIGuildWithJoinedAt",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]);
- if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
+ const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]);
+ if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
- return res.send({
- ...guild,
- joined_at: member?.joined_at,
- });
- },
+ return res.send({
+ ...guild,
+ joined_at: member?.joined_at,
+ });
+ },
);
router.patch(
- "/",
- route({
- requestBody: "GuildUpdateSchema",
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "GuildCreateResponse",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as GuildUpdateSchema;
- const { guild_id } = req.params;
+ "/",
+ route({
+ requestBody: "GuildUpdateSchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "GuildCreateResponse",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as GuildUpdateSchema;
+ const { guild_id } = req.params;
- const rights = await getRights(req.user_id);
- const permission = await getPermission(req.user_id, guild_id);
+ const rights = await getRights(req.user_id);
+ const permission = await getPermission(req.user_id, guild_id);
- if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
+ if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- relations: ["emojis", "roles", "stickers"],
- });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ relations: ["emojis", "roles", "stickers"],
+ });
- // trying to `select` this fails
- guild.channel_ordering = (
- await Guild.findOneOrFail({
- where: { id: guild_id },
- select: { channel_ordering: true },
- })
- ).channel_ordering;
+ // trying to `select` this fails
+ guild.channel_ordering = (
+ await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: { channel_ordering: true },
+ })
+ ).channel_ordering;
- // TODO: guild update check image
+ // TODO: guild update check image
- if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
+ if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
- if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
+ if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
- if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
+ if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
- if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
- body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash);
+ if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
+ body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash);
- if (body.features) {
- const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x)));
+ if (body.features) {
+ const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x)));
- // TODO move these
- const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
+ // TODO move these
+ const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
- for (const feature of diff) {
- if (MUTABLE_FEATURES.includes(feature)) continue;
+ for (const feature of diff) {
+ if (MUTABLE_FEATURES.includes(feature)) continue;
- throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
- }
+ throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
+ }
- // for some reason, they don't update in the assign.
- guild.features = body.features;
- }
+ // for some reason, they don't update in the assign.
+ guild.features = body.features;
+ }
- // TODO: check if body ids are valid
- guild.assign(body);
+ // TODO: check if body ids are valid
+ guild.assign(body);
- if (body.public_updates_channel_id == "1") {
- // create an updates channel for them
- const channel = await Channel.createChannel(
- {
- name: "moderator-only",
- guild_id: guild.id,
- position: 0,
- type: 0,
- permission_overwrites: [
- // remove SEND_MESSAGES from @everyone
- {
- id: guild.id,
- allow: "0",
- deny: Permissions.FLAGS.VIEW_CHANNEL.toString(),
- type: 0,
- },
- ],
- },
- undefined,
- { skipPermissionCheck: true },
- );
+ if (body.public_updates_channel_id == "1") {
+ // create an updates channel for them
+ const channel = await Channel.createChannel(
+ {
+ name: "moderator-only",
+ guild_id: guild.id,
+ position: 0,
+ type: 0,
+ permission_overwrites: [
+ // remove SEND_MESSAGES from @everyone
+ {
+ id: guild.id,
+ allow: "0",
+ deny: Permissions.FLAGS.VIEW_CHANNEL.toString(),
+ type: 0,
+ },
+ ],
+ },
+ undefined,
+ { skipPermissionCheck: true },
+ );
- await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
+ await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
- guild.public_updates_channel_id = channel.id;
- } else if (body.public_updates_channel_id != undefined) {
- // ensure channel exists in this guild
- await Channel.findOneOrFail({
- where: { guild_id, id: body.public_updates_channel_id },
- select: { id: true },
- });
- }
+ guild.public_updates_channel_id = channel.id;
+ } else if (body.public_updates_channel_id != undefined) {
+ // ensure channel exists in this guild
+ await Channel.findOneOrFail({
+ where: { guild_id, id: body.public_updates_channel_id },
+ select: { id: true },
+ });
+ }
- if (body.rules_channel_id == "1") {
- // create a rules for them
- const channel = await Channel.createChannel(
- {
- name: "rules",
- guild_id: guild.id,
- position: 0,
- type: 0,
- permission_overwrites: [
- // remove SEND_MESSAGES from @everyone
- {
- id: guild.id,
- allow: "0",
- deny: Permissions.FLAGS.SEND_MESSAGES.toString(),
- type: 0,
- },
- ],
- },
- undefined,
- { skipPermissionCheck: true },
- );
+ if (body.rules_channel_id == "1") {
+ // create a rules for them
+ const channel = await Channel.createChannel(
+ {
+ name: "rules",
+ guild_id: guild.id,
+ position: 0,
+ type: 0,
+ permission_overwrites: [
+ // remove SEND_MESSAGES from @everyone
+ {
+ id: guild.id,
+ allow: "0",
+ deny: Permissions.FLAGS.SEND_MESSAGES.toString(),
+ type: 0,
+ },
+ ],
+ },
+ undefined,
+ { skipPermissionCheck: true },
+ );
- await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
+ await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
- guild.rules_channel_id = channel.id;
- } else if (body.rules_channel_id != undefined) {
- // ensure channel exists in this guild
- await Channel.findOneOrFail({
- where: { guild_id, id: body.rules_channel_id },
- select: { id: true },
- });
- }
+ guild.rules_channel_id = channel.id;
+ } else if (body.rules_channel_id != undefined) {
+ // ensure channel exists in this guild
+ await Channel.findOneOrFail({
+ where: { guild_id, id: body.rules_channel_id },
+ select: { id: true },
+ });
+ }
- const data = guild.toJSON();
- // TODO: guild hashes
- // TODO: fix vanity_url_code, template_id
- // delete data.vanity_url_code;
- delete data.template_id;
+ const data = guild.toJSON();
+ // TODO: guild hashes
+ // TODO: fix vanity_url_code, template_id
+ // delete data.vanity_url_code;
+ delete data.template_id;
- await Promise.all([
- guild.save(),
- emitEvent({
- event: "GUILD_UPDATE",
- data,
- guild_id,
- } as GuildUpdateEvent),
- ]);
+ await Promise.all([
+ guild.save(),
+ emitEvent({
+ event: "GUILD_UPDATE",
+ data,
+ guild_id,
+ } as GuildUpdateEvent),
+ ]);
- return res.json(data);
- },
+ return res.json(data);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/integrations.ts b/src/api/routes/guilds/#guild_id/integrations.ts
index e396e2354..704b5a50b 100644
--- a/src/api/routes/guilds/#guild_id/integrations.ts
+++ b/src/api/routes/guilds/#guild_id/integrations.ts
@@ -22,6 +22,6 @@ const router = Router({ mergeParams: true });
//TODO: implement integrations list
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json([]);
+ res.json([]);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index 9372651ae..32d86a4a4 100644
--- a/src/api/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -23,33 +23,33 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "APIInviteArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "APIInviteArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const invites = await Invite.find({
- where: { guild_id },
- relations: PublicInviteRelation,
- });
+ const invites = await Invite.find({
+ where: { guild_id },
+ relations: PublicInviteRelation,
+ });
- await Promise.all(
- invites
- .filter((i) => i.isExpired())
- .map(async (i) => {
- await Invite.delete({ code: i.code });
- }),
- );
+ await Promise.all(
+ invites
+ .filter((i) => i.isExpired())
+ .map(async (i) => {
+ await Invite.delete({ code: i.code });
+ }),
+ );
- return res.json(invites.filter((i) => !i.isExpired()));
- },
+ return res.json(invites.filter((i) => !i.isExpired()));
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 20225e2bd..11b36a3cc 100644
--- a/src/api/routes/guilds/#guild_id/member-verification.ts
+++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -21,22 +21,22 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: member verification
+ "/",
+ route({
+ responses: {
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: member verification
- res.status(404).json({
- message: "Unknown Guild Member Verification Form",
- code: 10068,
- });
- },
+ res.status(404).json({
+ message: "Unknown Guild Member Verification Form",
+ code: 10068,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 9696cb6bf..81cda1824 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -24,212 +24,212 @@ import { MemberChangeSchema, PublicMemberProjection, PublicUserProjection } from
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIPublicMember",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, member_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPublicMember",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, member_id } = req.params;
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const member = await Member.findOneOrFail({
- where: { id: member_id, guild_id },
- relations: ["roles", "user"],
- select: {
- index: true,
- // only grab public member props
- ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])),
- // and public user props
- user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
- roles: {
- id: true,
- },
- },
- });
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ relations: ["roles", "user"],
+ select: {
+ index: true,
+ // only grab public member props
+ ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])),
+ // and public user props
+ user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
+ roles: {
+ id: true,
+ },
+ },
+ });
- return res.json({
- ...member.toPublicMember(),
- user: member.user.toPublicUser(),
- roles: member.roles.map((x) => x.id),
- });
- },
+ return res.json({
+ ...member.toPublicMember(),
+ user: member.user.toPublicUser(),
+ roles: member.roles.map((x) => x.id),
+ });
+ },
);
router.patch(
- "/",
- route({
- requestBody: "MemberChangeSchema",
- responses: {
- 200: {
- body: "Member",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id;
- const body = req.body as MemberChangeSchema;
+ "/",
+ route({
+ requestBody: "MemberChangeSchema",
+ responses: {
+ 200: {
+ body: "Member",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id;
+ const body = req.body as MemberChangeSchema;
- const member = await Member.findOneOrFail({
- where: { id: member_id, guild_id },
- relations: ["roles", "user"],
- });
- const permission = await getPermission(req.user_id, guild_id);
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ relations: ["roles", "user"],
+ });
+ const permission = await getPermission(req.user_id, guild_id);
- if ("nick" in body) {
- permission.hasThrow("MANAGE_NICKNAMES");
+ if ("nick" in body) {
+ permission.hasThrow("MANAGE_NICKNAMES");
- if (!body.nick) {
- delete body.nick;
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore shut up
- member.nick = null; // remove the nickname
- }
- }
+ if (!body.nick) {
+ delete body.nick;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore shut up
+ member.nick = null; // remove the nickname
+ }
+ }
- if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") {
- const rights = await getRights(req.user_id);
- rights.hasThrow("MANAGE_USERS");
- }
+ if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") {
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("MANAGE_USERS");
+ }
- if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
+ if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
- member.assign(body);
+ member.assign(body);
- // must do this after the assign because the body roles array
- // is string[] not Role[]
- if ("roles" in body) {
- permission.hasThrow("MANAGE_ROLES");
+ // must do this after the assign because the body roles array
+ // is string[] not Role[]
+ if ("roles" in body) {
+ permission.hasThrow("MANAGE_ROLES");
- body.roles = body.roles || [];
- body.roles.filter((x) => !!x);
+ body.roles = body.roles || [];
+ body.roles.filter((x) => !!x);
- if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id);
- // foreign key constraint will fail if role doesn't exist
- member.roles = body.roles.map((x) => Role.create({ id: x }));
- }
+ if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id);
+ // foreign key constraint will fail if role doesn't exist
+ member.roles = body.roles.map((x) => Role.create({ id: x }));
+ }
- if ("communication_disabled_until" in body) {
- permission.hasThrow("MODERATE_MEMBERS");
- member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until);
- }
+ if ("communication_disabled_until" in body) {
+ permission.hasThrow("MODERATE_MEMBERS");
+ member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until);
+ }
- await member.save();
+ await member.save();
- member.roles = member.roles.filter((x) => x.id !== guild_id);
+ member.roles = member.roles.filter((x) => x.id !== guild_id);
- // 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,
- data: { ...member, roles: member.roles.map((x) => x.id) },
- } as GuildMemberUpdateEvent);
+ // 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,
+ data: { ...member, roles: member.roles.map((x) => x.id) },
+ } as GuildMemberUpdateEvent);
- res.json(member);
- },
+ res.json(member);
+ },
);
router.put(
- "/",
- route({
- responses: {
- 200: {
- body: "MemberJoinGuildResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Lurker mode
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "MemberJoinGuildResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Lurker mode
- const rights = await getRights(req.user_id);
+ const rights = await getRights(req.user_id);
- const { guild_id } = req.params;
- let { member_id } = req.params;
- if (member_id === "@me") {
- member_id = req.user_id;
- rights.hasThrow("JOIN_GUILDS");
- if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
- } else {
- // TODO: check oauth2 scope
+ const { guild_id } = req.params;
+ let { member_id } = req.params;
+ if (member_id === "@me") {
+ member_id = req.user_id;
+ rights.hasThrow("JOIN_GUILDS");
+ if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
+ } else {
+ // TODO: check oauth2 scope
- throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE;
- }
+ throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE;
+ }
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
- if (!guild.features.includes("DISCOVERABLE")) {
- throw DiscordApiErrors.UNKNOWN_GUILD;
- }
+ if (!guild.features.includes("DISCOVERABLE")) {
+ throw DiscordApiErrors.UNKNOWN_GUILD;
+ }
- const emoji = await Emoji.find({
- where: { guild_id: guild_id },
- });
+ const emoji = await Emoji.find({
+ where: { guild_id: guild_id },
+ });
- const roles = await Role.find({
- where: { guild_id: guild_id },
- });
+ const roles = await Role.find({
+ where: { guild_id: guild_id },
+ });
- const stickers = await Sticker.find({
- where: { guild_id: guild_id },
- });
+ const stickers = await Sticker.find({
+ where: { guild_id: guild_id },
+ });
- await Member.addToGuild(member_id, guild_id);
- res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
- },
+ await Member.addToGuild(member_id, guild_id);
+ res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
+ },
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, member_id } = req.params;
- const permission = await getPermission(req.user_id, guild_id);
- const rights = await getRights(req.user_id);
- if (member_id === "@me" || member_id === req.user_id) {
- // TODO: unless force-joined
- rights.hasThrow("SELF_LEAVE_GROUPS");
- } else {
- rights.hasThrow("KICK_BAN_MEMBERS");
- permission.hasThrow("KICK_MEMBERS");
- }
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, member_id } = req.params;
+ const permission = await getPermission(req.user_id, guild_id);
+ const rights = await getRights(req.user_id);
+ if (member_id === "@me" || member_id === req.user_id) {
+ // TODO: unless force-joined
+ rights.hasThrow("SELF_LEAVE_GROUPS");
+ } else {
+ rights.hasThrow("KICK_BAN_MEMBERS");
+ permission.hasThrow("KICK_MEMBERS");
+ }
- await Member.removeFromGuild(member_id, guild_id);
- res.sendStatus(204);
- },
+ await Member.removeFromGuild(member_id, guild_id);
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 2a7077710..33f891522 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -23,38 +23,38 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.patch(
- "/",
- route({
- requestBody: "MemberNickChangeSchema",
- responses: {
- 200: {
- body: "APIPublicMember",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
- const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id;
+ "/",
+ route({
+ requestBody: "MemberNickChangeSchema",
+ responses: {
+ 200: {
+ body: "APIPublicMember",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
+ const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id;
- const perms = await getPermission(req.user_id, guild_id);
- perms.hasThrow(permissionString);
+ const perms = await getPermission(req.user_id, guild_id);
+ perms.hasThrow(permissionString);
- await Member.changeNickname(member_id, guild_id, req.body.nick);
+ await Member.changeNickname(member_id, guild_id, req.body.nick);
- const member = await Member.findOne({
- where: { id: member_id, guild_id },
- relations: ["roles"],
- });
+ const member = await Member.findOne({
+ where: { id: member_id, guild_id },
+ relations: ["roles"],
+ });
- res.send(member?.toPublicMember());
- },
+ res.send(member?.toPublicMember());
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 5a5bfdfd4..d482c06ec 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -23,39 +23,39 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.delete(
- "/",
- route({
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ 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);
- },
+ await Member.removeRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
);
router.put(
- "/",
- route({
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.addRole(member_id, guild_id, role_id);
- res.sendStatus(204);
- },
+ await Member.addRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index a047472c8..08b77076c 100644
--- a/src/api/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -29,44 +29,44 @@ const router = Router({ mergeParams: true });
// TODO: check for GUILD_MEMBERS intent
router.get(
- "/",
- route({
- query: {
- limit: {
- type: "number",
- description: "max number of members to return (1-1000). default 1",
- },
- after: {
- type: "string",
- },
- },
- responses: {
- 200: {
- body: "APIMemberArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- 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) } : {};
+ "/",
+ route({
+ query: {
+ limit: {
+ type: "number",
+ description: "max number of members to return (1-1000). default 1",
+ },
+ after: {
+ type: "string",
+ },
+ },
+ responses: {
+ 200: {
+ body: "APIMemberArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ 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);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const members = await Member.find({
- where: { guild_id, ...query },
- select: PublicMemberProjection,
- take: limit,
- order: { id: "ASC" },
- });
+ const members = await Member.find({
+ where: { guild_id, ...query },
+ select: PublicMemberProjection,
+ take: limit,
+ order: { id: "ASC" },
+ });
- return res.json(members);
- },
+ return res.json(members);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index 6532b53b2..be00d5ded 100644
--- a/src/api/routes/guilds/#guild_id/messages/search.ts
+++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -27,125 +27,125 @@ import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildMessagesSearchResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 422: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const {
- channel_id,
- content,
- // include_nsfw, // TODO
- offset,
- sort_order,
- // sort_by, // TODO: Handle 'relevance'
- limit,
- author_id,
- } = req.query;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildMessagesSearchResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 422: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const {
+ channel_id,
+ content,
+ // include_nsfw, // TODO
+ offset,
+ sort_order,
+ // sort_by, // TODO: Handle 'relevance'
+ limit,
+ author_id,
+ } = req.query;
- const parsedLimit = Number(limit) || 50;
- if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
+ const parsedLimit = Number(limit) || 50;
+ if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
- if (sort_order) {
- if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1)
- throw FieldErrors({
- sort_order: {
- message: "Value must be one of ('desc', 'asc').",
- code: "BASE_TYPE_CHOICES",
- },
- }); // todo this is wrong
- }
+ if (sort_order) {
+ if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1)
+ throw FieldErrors({
+ sort_order: {
+ message: "Value must be one of ('desc', 'asc').",
+ code: "BASE_TYPE_CHOICES",
+ },
+ }); // todo this is wrong
+ }
- const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined);
- permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
+ const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined);
+ permissions.hasThrow("VIEW_CHANNEL");
+ if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
- const query: FindManyOptions = {
- order: {
- timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
- },
- take: parsedLimit || 0,
- where: {
- guild: {
- id: req.params.guild_id,
- },
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- skip: offset ? Number(offset) : 0,
- };
- //@ts-ignore
- if (channel_id) query.where.channel = { id: channel_id };
- else {
- // get all channel IDs that this user can access
- const channels = await Channel.find({
- where: { guild_id: req.params.guild_id },
- select: ["id"],
- });
- const ids = [];
+ const query: FindManyOptions = {
+ order: {
+ timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
+ },
+ take: parsedLimit || 0,
+ where: {
+ guild: {
+ id: req.params.guild_id,
+ },
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ skip: offset ? Number(offset) : 0,
+ };
+ //@ts-ignore
+ if (channel_id) query.where.channel = { id: channel_id };
+ else {
+ // get all channel IDs that this user can access
+ const channels = await Channel.find({
+ where: { guild_id: req.params.guild_id },
+ select: ["id"],
+ });
+ const ids = [];
- for (const channel of channels) {
- const perm = await getPermission(req.user_id, req.params.guild_id, channel.id);
- if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue;
- ids.push(channel.id);
- }
+ for (const channel of channels) {
+ const perm = await getPermission(req.user_id, req.params.guild_id, channel.id);
+ if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue;
+ ids.push(channel.id);
+ }
- //@ts-ignore
- query.where.channel = { id: In(ids) };
- }
- //@ts-ignore
- if (author_id) query.where.author = { id: author_id };
- //@ts-ignore
- if (content) query.where.content = Like(`%${content}%`);
+ //@ts-ignore
+ query.where.channel = { id: In(ids) };
+ }
+ //@ts-ignore
+ if (author_id) query.where.author = { id: author_id };
+ //@ts-ignore
+ if (content) query.where.content = Like(`%${content}%`);
- const messages: Message[] = await Message.find(query);
- delete query.take;
- const total_results = await Message.count(query);
+ const messages: Message[] = await Message.find(query);
+ delete query.take;
+ const total_results = await Message.count(query);
- const messagesDto = messages.map((x) => [
- {
- id: x.id,
- type: x.type,
- content: x.content,
- channel_id: x.channel_id,
- author: {
- id: x.author?.id,
- username: x.author?.username,
- avatar: x.author?.avatar,
- avatar_decoration: null,
- discriminator: x.author?.discriminator,
- public_flags: x.author?.public_flags,
- },
- attachments: x.attachments,
- embeds: x.embeds,
- mentions: x.mentions,
- mention_roles: x.mention_roles,
- pinned: x.pinned,
- mention_everyone: x.mention_everyone,
- tts: x.tts,
- timestamp: x.timestamp,
- edited_timestamp: x.edited_timestamp,
- flags: x.flags,
- components: x.components,
- poll: x.poll,
- hit: true,
- },
- ]);
+ const messagesDto = messages.map((x) => [
+ {
+ id: x.id,
+ type: x.type,
+ content: x.content,
+ channel_id: x.channel_id,
+ author: {
+ id: x.author?.id,
+ username: x.author?.username,
+ avatar: x.author?.avatar,
+ avatar_decoration: null,
+ discriminator: x.author?.discriminator,
+ public_flags: x.author?.public_flags,
+ },
+ attachments: x.attachments,
+ embeds: x.embeds,
+ mentions: x.mentions,
+ mention_roles: x.mention_roles,
+ pinned: x.pinned,
+ mention_everyone: x.mention_everyone,
+ tts: x.tts,
+ timestamp: x.timestamp,
+ edited_timestamp: x.edited_timestamp,
+ flags: x.flags,
+ components: x.components,
+ poll: x.poll,
+ hit: true,
+ },
+ ]);
- return res.json({
- messages: messagesDto,
- total_results,
- });
- },
+ return res.json({
+ messages: messagesDto,
+ total_results,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/premium.ts b/src/api/routes/guilds/#guild_id/premium.ts
index b72ded264..fc0d34bf5 100644
--- a/src/api/routes/guilds/#guild_id/premium.ts
+++ b/src/api/routes/guilds/#guild_id/premium.ts
@@ -21,8 +21,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/subscriptions", route({}), async (req: Request, res: Response) => {
- // TODO:
- res.json([]);
+ // TODO:
+ res.json([]);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/profile.ts b/src/api/routes/guilds/#guild_id/profile.ts
index 4b263fb0b..176c612c9 100644
--- a/src/api/routes/guilds/#guild_id/profile.ts
+++ b/src/api/routes/guilds/#guild_id/profile.ts
@@ -24,43 +24,43 @@ import { GuildProfileResponse, GuildVisibilityLevel } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- "200": {
- body: "GuildProfileResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- const profileResponse: GuildProfileResponse = {
- id: guild_id,
- name: guild.name,
- icon_hash: guild.icon ?? null,
- member_count: guild.member_count!,
- online_count: guild.member_count!,
- description: guild.description ?? "A Spacebar guild",
- brand_color_primary: "#FF00FF",
- banner_hash: null,
- game_application_ids: [], // We don't track this
- game_activity: {}, // We don't track this
- tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags
- badge: 0,
- badge_color_primary: "#FF00FF",
- badge_color_secondary: "#00FFFF",
- badge_hash: "",
- traits: [],
- features: guild.features ?? [],
- visibility: GuildVisibilityLevel.PUBLIC,
- custom_banner_hash: guild.banner ?? null,
- premium_subscription_count: guild.premium_subscription_count ?? 0,
- premium_tier: guild.premium_tier ?? 0,
- };
+ "/",
+ route({
+ responses: {
+ "200": {
+ body: "GuildProfileResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const profileResponse: GuildProfileResponse = {
+ id: guild_id,
+ name: guild.name,
+ icon_hash: guild.icon ?? null,
+ member_count: guild.member_count!,
+ online_count: guild.member_count!,
+ description: guild.description ?? "A Spacebar guild",
+ brand_color_primary: "#FF00FF",
+ banner_hash: null,
+ game_application_ids: [], // We don't track this
+ game_activity: {}, // We don't track this
+ tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags
+ badge: 0,
+ badge_color_primary: "#FF00FF",
+ badge_color_secondary: "#00FFFF",
+ badge_hash: "",
+ traits: [],
+ features: guild.features ?? [],
+ visibility: GuildVisibilityLevel.PUBLIC,
+ custom_banner_hash: guild.banner ?? null,
+ premium_subscription_count: guild.premium_subscription_count ?? 0,
+ premium_tier: guild.premium_tier ?? 0,
+ };
- res.send(profileResponse);
- },
+ res.send(profileResponse);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts
index 8f0e38574..7a944b1fc 100644
--- a/src/api/routes/guilds/#guild_id/profile/index.ts
+++ b/src/api/routes/guilds/#guild_id/profile/index.ts
@@ -24,47 +24,47 @@ import { MemberChangeProfileSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.patch(
- "/:member_id",
- route({
- requestBody: "MemberChangeProfileSchema",
- responses: {
- 200: {
- body: "Member",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // const member_id =
- // req.params.member_id === "@me" ? req.user_id : req.params.member_id;
- const body = req.body as MemberChangeProfileSchema;
+ "/:member_id",
+ route({
+ requestBody: "MemberChangeProfileSchema",
+ responses: {
+ 200: {
+ body: "Member",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ // const member_id =
+ // req.params.member_id === "@me" ? req.user_id : req.params.member_id;
+ const body = req.body as MemberChangeProfileSchema;
- let member = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id },
- relations: ["roles", "user"],
- });
+ let member = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id },
+ relations: ["roles", "user"],
+ });
- if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
+ if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
- member = await OrmUtils.mergeDeep(member, body);
+ member = await OrmUtils.mergeDeep(member, body);
- await member.save();
+ await member.save();
- // 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,
- data: { ...member, roles: member.roles.map((x) => x.id) },
- } as GuildMemberUpdateEvent);
+ // 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,
+ data: { ...member, roles: member.roles.map((x) => x.id) },
+ } as GuildMemberUpdateEvent);
- res.json(member);
- },
+ res.json(member);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 47984e025..c8a42b848 100644
--- a/src/api/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -24,102 +24,102 @@ const router = Router({ mergeParams: true });
//Returns all inactive members, respecting role hierarchy
const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
- const date = new Date();
- date.setDate(date.getDate() - days);
- //Snowflake should have `generateFromTime` method? Or similar?
- const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
+ const date = new Date();
+ date.setDate(date.getDate() - days);
+ //Snowflake should have `generateFromTime` method? Or similar?
+ const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
- /**
+ /**
idea: ability to customise the cutoff variable
possible candidates: public read receipt, last presence, last VC leave
**/
- let members = await Member.find({
- where: [
- {
- guild_id,
- last_message_id: LessThan(minId.toString()),
- },
- {
- guild_id,
- last_message_id: IsNull(),
- },
- ],
- relations: ["roles"],
- });
- if (!members.length) return [];
+ let members = await Member.find({
+ where: [
+ {
+ guild_id,
+ last_message_id: LessThan(minId.toString()),
+ },
+ {
+ guild_id,
+ last_message_id: IsNull(),
+ },
+ ],
+ relations: ["roles"],
+ });
+ if (!members.length) return [];
- //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
- if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
+ //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
+ if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
- const me = await Member.findOneOrFail({
- where: { id: user_id, guild_id },
- relations: ["roles"],
- });
- const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
+ const me = await Member.findOneOrFail({
+ where: { id: user_id, guild_id },
+ relations: ["roles"],
+ });
+ const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- members = members.filter(
- (member) =>
- member.id !== guild.owner_id && //can't kick owner
- member.roles?.some(
- (role) =>
- role.position < myHighestRole || //roles higher than me can't be kicked
- me.id === guild.owner_id, //owner can kick anyone
- ),
- );
+ members = members.filter(
+ (member) =>
+ member.id !== guild.owner_id && //can't kick owner
+ member.roles?.some(
+ (role) =>
+ role.position < myHighestRole || //roles higher than me can't be kicked
+ me.id === guild.owner_id, //owner can kick anyone
+ ),
+ );
- return members;
+ return members;
};
router.get(
- "/",
- route({
- responses: {
- "200": {
- body: "GuildPruneResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const days = parseInt(req.query.days as string);
+ "/",
+ route({
+ responses: {
+ "200": {
+ body: "GuildPruneResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const days = parseInt(req.query.days as string);
- let roles = req.query.include_roles;
- if (typeof roles === "string") roles = [roles]; //express will return array otherwise
+ let roles = req.query.include_roles;
+ if (typeof roles === "string") roles = [roles]; //express will return array otherwise
- const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]);
+ const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]);
- res.send({ pruned: members.length });
- },
+ res.send({ pruned: members.length });
+ },
);
router.post(
- "/",
- route({
- permission: "KICK_MEMBERS",
- right: "KICK_BAN_MEMBERS",
- responses: {
- 200: {
- body: "GuildPurgeResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const days = parseInt(req.body.days);
+ "/",
+ route({
+ permission: "KICK_MEMBERS",
+ right: "KICK_BAN_MEMBERS",
+ responses: {
+ 200: {
+ body: "GuildPurgeResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const days = parseInt(req.body.days);
- let roles = req.query.include_roles;
- if (typeof roles === "string") roles = [roles];
+ let roles = req.query.include_roles;
+ if (typeof roles === "string") roles = [roles];
- const { guild_id } = req.params;
- const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
+ const { guild_id } = req.params;
+ const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
- await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
+ await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
- res.send({ purged: members.length });
- },
+ res.send({ purged: members.length });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index a3c5a81b6..b2a311d71 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -23,23 +23,23 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGuildVoiceRegion",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- //TODO we should use an enum for guild's features and not hardcoded strings
- return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS")));
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildVoiceRegion",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ //TODO we should use an enum for guild's features and not hardcoded strings
+ return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS")));
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index b8349eb6b..1811284a9 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -25,121 +25,121 @@ import { RoleModifySchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Role",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, role_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
- const role = await Role.findOneOrFail({
- where: { guild_id, id: role_id },
- });
- return res.json(role);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Role",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id } = req.params;
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
+ const role = await Role.findOneOrFail({
+ where: { guild_id, id: role_id },
+ });
+ return res.json(role);
+ },
);
router.delete(
- "/",
- route({
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, role_id } = req.params;
- if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
+ "/",
+ route({
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id } = req.params;
+ if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
- await Promise.all([
- Role.delete({
- id: role_id,
- guild_id: guild_id,
- }),
- emitEvent({
- event: "GUILD_ROLE_DELETE",
- guild_id,
- data: {
- guild_id,
- role_id,
- },
- } as GuildRoleDeleteEvent),
- ]);
+ await Promise.all([
+ Role.delete({
+ id: role_id,
+ guild_id: guild_id,
+ }),
+ emitEvent({
+ event: "GUILD_ROLE_DELETE",
+ guild_id,
+ data: {
+ guild_id,
+ role_id,
+ },
+ } as GuildRoleDeleteEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
// TODO: check role hierarchy
router.patch(
- "/",
- route({
- requestBody: "RoleModifySchema",
- permission: "MANAGE_ROLES",
- responses: {
- 200: {
- body: "Role",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { role_id, guild_id } = req.params;
- const body = req.body as RoleModifySchema;
+ "/",
+ route({
+ requestBody: "RoleModifySchema",
+ permission: "MANAGE_ROLES",
+ responses: {
+ 200: {
+ body: "Role",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { role_id, guild_id } = req.params;
+ const body = req.body as RoleModifySchema;
- if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
- else body.icon = undefined;
+ if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
+ else body.icon = undefined;
- const role = await Role.findOneOrFail({
- where: { id: role_id, guild: { id: guild_id } },
- });
- role.assign({
- ...body,
- permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
- });
+ const role = await Role.findOneOrFail({
+ where: { id: role_id, guild: { id: guild_id } },
+ });
+ role.assign({
+ ...body,
+ permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
+ });
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
- guild_id,
- data: {
- guild_id,
- role,
- },
- } as GuildRoleUpdateEvent),
- ]);
+ await Promise.all([
+ role.save(),
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
+ guild_id,
+ data: {
+ guild_id,
+ role,
+ },
+ } as GuildRoleUpdateEvent),
+ ]);
- res.json(role);
- },
+ res.json(role);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
index 6558da58e..ab61325e4 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
@@ -23,20 +23,20 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id, role_id } = req.params;
+ const { guild_id, role_id } = req.params;
- // TODO: Is this route really not paginated?
- const members = await Member.find({
- select: ["id"],
- where: {
- roles: {
- id: role_id,
- },
- guild_id,
- },
- });
+ // TODO: Is this route really not paginated?
+ const members = await Member.find({
+ select: ["id"],
+ where: {
+ roles: {
+ id: role_id,
+ },
+ guild_id,
+ },
+ });
- return res.json(members.map((x) => x.id));
+ return res.json(members.map((x) => x.id));
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
index d22d14347..f89d59350 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
@@ -23,24 +23,24 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.patch("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- // Payload is JSON containing a list of member_ids, the new list of members to have the role
- const { guild_id, role_id } = req.params;
- const { member_ids } = req.body;
+ // Payload is JSON containing a list of member_ids, the new list of members to have the role
+ const { guild_id, role_id } = req.params;
+ const { member_ids } = req.body;
- // don't mess with @everyone
- if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE;
+ // don't mess with @everyone
+ if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE;
- const members = await Member.find({
- where: { guild_id },
- relations: ["roles"],
- });
+ const members = await Member.find({
+ where: { guild_id },
+ relations: ["roles"],
+ });
- const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id));
+ const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id));
- // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
- await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]);
+ // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
+ await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]);
- res.sendStatus(204);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index 26584e905..8caea56fc 100644
--- a/src/api/routes/guilds/#guild_id/roles/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -25,129 +25,129 @@ import { RoleModifySchema, RolePositionUpdateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
+ const guild_id = req.params.guild_id;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const roles = await Role.find({ where: { guild_id: guild_id } });
+ const roles = await Role.find({ where: { guild_id: guild_id } });
- return res.json(roles);
+ return res.json(roles);
});
router.post(
- "/",
- route({
- requestBody: "RoleModifySchema",
- permission: "MANAGE_ROLES",
- responses: {
- 200: {
- body: "Role",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const body = req.body as RoleModifySchema;
+ "/",
+ route({
+ requestBody: "RoleModifySchema",
+ permission: "MANAGE_ROLES",
+ responses: {
+ 200: {
+ body: "Role",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
+ const body = req.body as RoleModifySchema;
- const role_count = await Role.count({ where: { guild_id } });
- const { maxRoles } = Config.get().limits.guild;
+ const role_count = await Role.count({ where: { guild_id } });
+ const { maxRoles } = Config.get().limits.guild;
- if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
+ if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
- const role = Role.create({
- // values before ...body are default and can be overridden
- position: 1,
- hoist: false,
- color: 0,
- mentionable: false,
- ...body,
- guild_id: guild_id,
- managed: false,
- permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
- tags: undefined,
- icon: undefined,
- unicode_emoji: undefined,
- id: Snowflake.generate(),
- colors: {
- primary_color: body.colors?.primary_color || body.color || 0,
- secondary_color: body.colors?.secondary_color || undefined, // gradient
- tertiary_color: body.colors?.tertiary_color || undefined, // "holographic"
- },
- });
+ const role = Role.create({
+ // values before ...body are default and can be overridden
+ position: 1,
+ hoist: false,
+ color: 0,
+ mentionable: false,
+ ...body,
+ guild_id: guild_id,
+ managed: false,
+ permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
+ tags: undefined,
+ icon: undefined,
+ unicode_emoji: undefined,
+ id: Snowflake.generate(),
+ colors: {
+ primary_color: body.colors?.primary_color || body.color || 0,
+ secondary_color: body.colors?.secondary_color || undefined, // gradient
+ tertiary_color: body.colors?.tertiary_color || undefined, // "holographic"
+ },
+ });
- await Promise.all([
- role.save(),
- // Move all existing roles up one position, to accommodate the new role
- Role.createQueryBuilder("roles")
- .where({
- guild: { id: guild_id },
- name: Not("@everyone"),
- id: Not(role.id),
- })
- .update({ position: () => "position + 1" })
- .execute(),
- emitEvent({
- event: "GUILD_ROLE_CREATE",
- guild_id,
- data: {
- guild_id,
- role: role,
- },
- } as GuildRoleCreateEvent),
- ]);
+ await Promise.all([
+ role.save(),
+ // Move all existing roles up one position, to accommodate the new role
+ Role.createQueryBuilder("roles")
+ .where({
+ guild: { id: guild_id },
+ name: Not("@everyone"),
+ id: Not(role.id),
+ })
+ .update({ position: () => "position + 1" })
+ .execute(),
+ emitEvent({
+ event: "GUILD_ROLE_CREATE",
+ guild_id,
+ data: {
+ guild_id,
+ role: role,
+ },
+ } as GuildRoleCreateEvent),
+ ]);
- res.json(role);
- },
+ res.json(role);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "RolePositionUpdateSchema",
- permission: "MANAGE_ROLES",
- responses: {
- 200: {
- body: "APIRoleArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as RolePositionUpdateSchema;
+ "/",
+ route({
+ requestBody: "RolePositionUpdateSchema",
+ permission: "MANAGE_ROLES",
+ responses: {
+ 200: {
+ body: "APIRoleArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as RolePositionUpdateSchema;
- await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
+ await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
- const roles = await Role.find({
- where: body.map((x) => ({ id: x.id, guild_id })),
- });
+ const roles = await Role.find({
+ where: body.map((x) => ({ id: x.id, guild_id })),
+ });
- await Promise.all(
- roles.map((x) =>
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
- guild_id,
- data: {
- guild_id,
- role: x,
- },
- } as GuildRoleUpdateEvent),
- ),
- );
+ await Promise.all(
+ roles.map((x) =>
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
+ guild_id,
+ data: {
+ guild_id,
+ role: x,
+ },
+ } as GuildRoleUpdateEvent),
+ ),
+ );
- res.json(roles);
- },
+ res.json(roles);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/member-counts.ts b/src/api/routes/guilds/#guild_id/roles/member-counts.ts
index 5574e59ec..d77149d5c 100644
--- a/src/api/routes/guilds/#guild_id/roles/member-counts.ts
+++ b/src/api/routes/guilds/#guild_id/roles/member-counts.ts
@@ -23,16 +23,16 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ const { guild_id } = req.params;
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const role_ids = await Role.find({ where: { guild_id }, select: ["id"] });
- const counts: { [id: string]: number } = {};
- for (const { id } of role_ids) {
- counts[id] = await Member.count({ where: { roles: { id }, guild_id } });
- }
+ const role_ids = await Role.find({ where: { guild_id }, select: ["id"] });
+ const counts: { [id: string]: number } = {};
+ for (const { id } of role_ids) {
+ counts[id] = await Member.count({ where: { roles: { id }, guild_id } });
+ }
- return res.json(counts);
+ return res.json(counts);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 231c3b550..f3a9f5276 100644
--- a/src/api/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -25,185 +25,185 @@ import { ModifyGuildStickerSchema, StickerFormatType, StickerType } from "@space
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIStickerArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIStickerArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(await Sticker.find({ where: { guild_id } }));
- },
+ res.json(await Sticker.find({ where: { guild_id } }));
+ },
);
const bodyParser = multer({
- limits: {
- fileSize: 1024 * 1024 * 100,
- fields: 10,
- files: 1,
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: 1024 * 1024 * 100,
+ fields: 10,
+ files: 1,
+ },
+ storage: multer.memoryStorage(),
}).single("file");
router.post(
- "/",
- bodyParser,
- route({
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- requestBody: "ModifyGuildStickerSchema",
- responses: {
- 200: {
- body: "Sticker",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (!req.file) throw new HTTPError("missing file");
+ "/",
+ bodyParser,
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ requestBody: "ModifyGuildStickerSchema",
+ responses: {
+ 200: {
+ body: "Sticker",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!req.file) throw new HTTPError("missing file");
- const { guild_id } = req.params;
- const body = req.body as ModifyGuildStickerSchema;
- const id = Snowflake.generate();
+ const { guild_id } = req.params;
+ const body = req.body as ModifyGuildStickerSchema;
+ const id = Snowflake.generate();
- const sticker_count = await Sticker.count({
- where: { guild_id: guild_id },
- });
- const { maxStickers } = Config.get().limits.guild;
+ const sticker_count = await Sticker.count({
+ where: { guild_id: guild_id },
+ });
+ const { maxStickers } = Config.get().limits.guild;
- if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers);
+ if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers);
- const [sticker] = await Promise.all([
- Sticker.create({
- ...body,
- guild_id,
- id,
- type: StickerType.GUILD,
- format_type: getStickerFormat(req.file.mimetype),
- available: true,
- }).save(),
- uploadFile(`/stickers/${id}`, req.file),
- ]);
+ const [sticker] = await Promise.all([
+ Sticker.create({
+ ...body,
+ guild_id,
+ id,
+ type: StickerType.GUILD,
+ format_type: getStickerFormat(req.file.mimetype),
+ available: true,
+ }).save(),
+ uploadFile(`/stickers/${id}`, req.file),
+ ]);
- await sendStickerUpdateEvent(guild_id);
+ await sendStickerUpdateEvent(guild_id);
- res.json(sticker);
- },
+ res.json(sticker);
+ },
);
function getStickerFormat(mime_type: string) {
- switch (mime_type) {
- case "image/apng":
- return StickerFormatType.APNG;
- case "application/json":
- return StickerFormatType.LOTTIE;
- case "image/png":
- return StickerFormatType.PNG;
- case "image/gif":
- return StickerFormatType.GIF;
- default:
- throw new HTTPError("invalid sticker format: must be png, apng or lottie");
- }
+ switch (mime_type) {
+ case "image/apng":
+ return StickerFormatType.APNG;
+ case "application/json":
+ return StickerFormatType.LOTTIE;
+ case "image/png":
+ return StickerFormatType.PNG;
+ case "image/gif":
+ return StickerFormatType.GIF;
+ default:
+ throw new HTTPError("invalid sticker format: must be png, apng or lottie");
+ }
}
router.get(
- "/:sticker_id",
- route({
- responses: {
- 200: {
- body: "Sticker",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, sticker_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ "/:sticker_id",
+ route({
+ responses: {
+ 200: {
+ body: "Sticker",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, sticker_id } = req.params;
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(
- await Sticker.findOneOrFail({
- where: { guild_id, id: sticker_id },
- }),
- );
- },
+ res.json(
+ await Sticker.findOneOrFail({
+ where: { guild_id, id: sticker_id },
+ }),
+ );
+ },
);
router.patch(
- "/:sticker_id",
- route({
- requestBody: "ModifyGuildStickerSchema",
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 200: {
- body: "Sticker",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, sticker_id } = req.params;
- const body = req.body as ModifyGuildStickerSchema;
+ "/:sticker_id",
+ route({
+ requestBody: "ModifyGuildStickerSchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 200: {
+ body: "Sticker",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, sticker_id } = req.params;
+ const body = req.body as ModifyGuildStickerSchema;
- const sticker = await Sticker.create({
- ...body,
- guild_id,
- id: sticker_id,
- }).save();
- await sendStickerUpdateEvent(guild_id);
+ const sticker = await Sticker.create({
+ ...body,
+ guild_id,
+ id: sticker_id,
+ }).save();
+ await sendStickerUpdateEvent(guild_id);
- return res.json(sticker);
- },
+ return res.json(sticker);
+ },
);
async function sendStickerUpdateEvent(guild_id: string) {
- return emitEvent({
- event: "GUILD_STICKERS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- stickers: await Sticker.find({ where: { guild_id: guild_id } }),
- },
- } as GuildStickersUpdateEvent);
+ return emitEvent({
+ event: "GUILD_STICKERS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ stickers: await Sticker.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildStickersUpdateEvent);
}
router.delete(
- "/:sticker_id",
- route({
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, sticker_id } = req.params;
+ "/:sticker_id",
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, sticker_id } = req.params;
- await Sticker.delete({ guild_id, id: sticker_id });
- await sendStickerUpdateEvent(guild_id);
+ await Sticker.delete({ guild_id, id: sticker_id });
+ await sendStickerUpdateEvent(guild_id);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index ef9d12c60..2cc9604ca 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -24,160 +24,160 @@ import { HTTPError } from "lambert-server";
const router: Router = Router({ mergeParams: true });
const TemplateGuildProjection: (keyof Guild)[] = [
- "id",
- "name",
- "description",
- "region",
- "verification_level",
- "default_message_notifications",
- "explicit_content_filter",
- "preferred_locale",
- "afk_timeout",
- // "roles",
- // "channels",
- "afk_channel_id",
- "system_channel_id",
- "system_channel_flags",
- "icon",
+ "id",
+ "name",
+ "description",
+ "region",
+ "verification_level",
+ "default_message_notifications",
+ "explicit_content_filter",
+ "preferred_locale",
+ "afk_timeout",
+ // "roles",
+ // "channels",
+ "afk_channel_id",
+ "system_channel_id",
+ "system_channel_flags",
+ "icon",
];
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APITemplateArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APITemplateArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const templates = await Template.find({
- where: { source_guild_id: guild_id },
- });
+ const templates = await Template.find({
+ where: { source_guild_id: guild_id },
+ });
- return res.json(templates);
- },
+ return res.json(templates);
+ },
);
router.post(
- "/",
- route({
- requestBody: "TemplateCreateSchema",
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "Template",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: TemplateGuildProjection,
- relations: ["roles", "channels"],
- });
- const exists = await Template.findOne({
- where: { id: guild_id },
- });
- if (exists) throw new HTTPError("Template already exists", 400);
+ "/",
+ route({
+ requestBody: "TemplateCreateSchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "Template",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ relations: ["roles", "channels"],
+ });
+ const exists = await Template.findOne({
+ where: { id: guild_id },
+ });
+ if (exists) throw new HTTPError("Template already exists", 400);
- const template = await Template.create({
- ...req.body,
- code: generateCode(),
- creator_id: req.user_id,
- created_at: new Date(),
- updated_at: new Date(),
- source_guild_id: guild_id,
- serialized_source_guild: guild,
- }).save();
+ const template = await Template.create({
+ ...req.body,
+ code: generateCode(),
+ creator_id: req.user_id,
+ created_at: new Date(),
+ updated_at: new Date(),
+ source_guild_id: guild_id,
+ serialized_source_guild: guild,
+ }).save();
- res.json(template);
- },
+ res.json(template);
+ },
);
router.delete(
- "/:code",
- route({
- permission: "MANAGE_GUILD",
- responses: {
- 200: { body: "Template" },
- 403: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
+ "/:code",
+ route({
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: { body: "Template" },
+ 403: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
- const template = await Template.delete({
- code,
- source_guild_id: guild_id,
- });
+ const template = await Template.delete({
+ code,
+ source_guild_id: guild_id,
+ });
- res.json(template);
- },
+ res.json(template);
+ },
);
router.put(
- "/:code",
- route({
- permission: "MANAGE_GUILD",
- responses: {
- 200: { body: "Template" },
- 403: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: TemplateGuildProjection,
- });
+ "/:code",
+ route({
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: { body: "Template" },
+ 403: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ });
- const template = await Template.create({
- code,
- serialized_source_guild: guild,
- }).save();
+ const template = await Template.create({
+ code,
+ serialized_source_guild: guild,
+ }).save();
- res.json(template);
- },
+ res.json(template);
+ },
);
router.patch(
- "/:code",
- route({
- requestBody: "TemplateModifySchema",
- permission: "MANAGE_GUILD",
- responses: {
- 200: { body: "Template" },
- 403: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
- const { name, description } = req.body;
+ "/:code",
+ route({
+ requestBody: "TemplateModifySchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: { body: "Template" },
+ 403: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+ const { name, description } = req.body;
- const template = await Template.findOneOrFail({
- where: { code, source_guild_id: guild_id },
- });
+ const template = await Template.findOneOrFail({
+ where: { code, source_guild_id: guild_id },
+ });
- template.name = name;
- template.description = description;
+ template.name = name;
+ template.description = description;
- await template.save();
+ await template.save();
- res.json(template);
- },
+ res.json(template);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index 77d72ed04..e622c2555 100644
--- a/src/api/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -27,96 +27,96 @@ const router = Router({ mergeParams: true });
const InviteRegex = /\W/g;
router.get(
- "/",
- route({
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "GuildVanityUrlResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ "/",
+ route({
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "GuildVanityUrlResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.features.includes("ALIASABLE_NAMES")) {
- const invite = await Invite.findOne({
- where: { guild_id: guild_id, vanity_url: true },
- });
- if (!invite) return res.json({ code: null });
+ if (!guild.features.includes("ALIASABLE_NAMES")) {
+ const invite = await Invite.findOne({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite) return res.json({ code: null });
- return res.json({ code: invite.code, uses: invite.uses });
- } else {
- const invite = await Invite.find({
- where: { guild_id: guild_id, vanity_url: true },
- });
- if (!invite || invite.length == 0) return res.json({ code: null });
+ return res.json({ code: invite.code, uses: invite.uses });
+ } else {
+ const invite = await Invite.find({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite || invite.length == 0) return res.json({ code: null });
- return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
- }
- },
+ return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
+ }
+ },
);
router.patch(
- "/",
- route({
- requestBody: "VanityUrlSchema",
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "GuildVanityUrlCreateResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as VanityUrlSchema;
- const code = body.code?.replace(InviteRegex, "");
+ "/",
+ route({
+ requestBody: "VanityUrlSchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "GuildVanityUrlCreateResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as VanityUrlSchema;
+ const code = body.code?.replace(InviteRegex, "");
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
- if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
+ if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
- const invite = await Invite.findOne({ where: { code } });
- if (invite) throw new HTTPError("Invite already exists");
+ const invite = await Invite.findOne({ where: { code } });
+ if (invite) throw new HTTPError("Invite already exists");
- const { id } = await Channel.findOneOrFail({
- where: { guild_id, type: ChannelType.GUILD_TEXT },
- });
+ const { id } = await Channel.findOneOrFail({
+ where: { guild_id, type: ChannelType.GUILD_TEXT },
+ });
- if (!guild.features.includes("ALIASABLE_NAMES")) {
- await Invite.delete({ guild_id, vanity_url: true });
- }
+ if (!guild.features.includes("ALIASABLE_NAMES")) {
+ await Invite.delete({ guild_id, vanity_url: true });
+ }
- await Invite.create({
- vanity_url: true,
- code,
- temporary: false,
- uses: 0,
- max_uses: 0,
- max_age: 0,
- created_at: new Date(),
- guild_id: guild_id,
- channel_id: id,
- flags: 0,
- }).save();
+ await Invite.create({
+ vanity_url: true,
+ code,
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ max_age: 0,
+ created_at: new Date(),
+ guild_id: guild_id,
+ channel_id: id,
+ flags: 0,
+ }).save();
- return res.json({ code });
- },
+ return res.json({ code });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 38be139cb..5283ae01e 100644
--- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -25,67 +25,67 @@ const router = Router({ mergeParams: true });
//TODO need more testing when community guild and voice stage channel are working
router.patch(
- "/",
- route({
- requestBody: "VoiceStateUpdateSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as VoiceStateUpdateSchema;
- const { guild_id } = req.params;
- const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id;
+ "/",
+ route({
+ requestBody: "VoiceStateUpdateSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as VoiceStateUpdateSchema;
+ const { guild_id } = req.params;
+ const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id;
- const perms = await getPermission(req.user_id, guild_id, body.channel_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");
+ 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({
- where: {
- guild_id,
- channel_id: body.channel_id,
- user_id,
- },
- });
- if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
+ const voice_state = await VoiceState.findOne({
+ where: {
+ 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({
- where: { guild_id, id: body.channel_id },
- });
- if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
- throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
- }
+ voice_state.assign(body);
+ const channel = await Channel.findOneOrFail({
+ where: { 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.toPublicVoiceState(),
- guild_id,
- } as VoiceStateUpdateEvent),
- ]);
- return res.sendStatus(204);
- },
+ await Promise.all([
+ voice_state.save(),
+ emitEvent({
+ event: "VOICE_STATE_UPDATE",
+ data: voice_state.toPublicVoiceState(),
+ guild_id,
+ } as VoiceStateUpdateEvent),
+ ]);
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/webhooks.ts b/src/api/routes/guilds/#guild_id/webhooks.ts
index 49f34b6bd..81837d34a 100644
--- a/src/api/routes/guilds/#guild_id/webhooks.ts
+++ b/src/api/routes/guilds/#guild_id/webhooks.ts
@@ -22,31 +22,31 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.",
- permission: "MANAGE_WEBHOOKS",
- responses: {
- 200: {
- body: "APIWebhookArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const webhooks = await Webhook.find({
- where: { guild_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.",
+ permission: "MANAGE_WEBHOOKS",
+ responses: {
+ 200: {
+ body: "APIWebhookArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const webhooks = await Webhook.find({
+ where: { guild_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- const instanceUrl = Config.get().api.endpointPublic;
- return res.json(
- webhooks.map((webhook) => ({
- ...webhook,
- url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token,
- })),
- );
- },
+ const instanceUrl = Config.get().api.endpointPublic;
+ return res.json(
+ webhooks.map((webhook) => ({
+ ...webhook,
+ url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token,
+ })),
+ );
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index a8956eea1..474d59779 100644
--- a/src/api/routes/guilds/#guild_id/welcome-screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -24,69 +24,69 @@ import { GuildUpdateWelcomeScreenSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildWelcomeScreen",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildWelcomeScreen",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(guild.welcome_screen);
- },
+ res.json(guild.welcome_screen);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "GuildUpdateWelcomeScreenSchema",
- permission: "MANAGE_GUILD",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const body = req.body as GuildUpdateWelcomeScreenSchema;
+ "/",
+ route({
+ requestBody: "GuildUpdateWelcomeScreenSchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
+ const body = req.body as GuildUpdateWelcomeScreenSchema;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled;
+ if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled;
- if (body.description != undefined) guild.welcome_screen.description = body.description;
+ if (body.description != undefined) guild.welcome_screen.description = body.description;
- if (body.welcome_channels != undefined) {
- // Ensure channels exist within the guild
- await Promise.all(
- body.welcome_channels?.map(({ channel_id }) =>
- Channel.findOneOrFail({
- where: { id: channel_id, guild_id },
- select: { id: true },
- }),
- ) || [],
- );
- guild.welcome_screen.welcome_channels = body.welcome_channels;
- }
+ if (body.welcome_channels != undefined) {
+ // Ensure channels exist within the guild
+ await Promise.all(
+ body.welcome_channels?.map(({ channel_id }) =>
+ Channel.findOneOrFail({
+ where: { id: channel_id, guild_id },
+ select: { id: true },
+ }),
+ ) || [],
+ );
+ guild.welcome_screen.welcome_channels = body.welcome_channels;
+ }
- await guild.save();
+ await guild.save();
- res.status(200).json(guild.welcome_screen);
- },
+ res.status(200).json(guild.welcome_screen);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index d6f958f7d..8f41417a8 100644
--- a/src/api/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -32,88 +32,88 @@ const router: Router = Router({ mergeParams: true });
// 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(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildWidgetJsonResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildWidgetJsonResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: {
- channel_ordering: true,
- widget_channel_id: true,
- widget_enabled: true,
- presence_count: true,
- name: true,
- },
- });
- if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: {
+ channel_ordering: true,
+ widget_channel_id: true,
+ widget_enabled: true,
+ presence_count: true,
+ name: true,
+ },
+ });
+ if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
- // Fetch existing widget invite for widget channel
- let invite = await Invite.findOne({
- where: { channel_id: guild.widget_channel_id },
- });
+ // Fetch existing widget invite for widget channel
+ let invite = await Invite.findOne({
+ where: { channel_id: guild.widget_channel_id },
+ });
- if (guild.widget_channel_id && !invite) {
- // Create invite for channel if none exists
- // TODO: Refactor invite create code to a shared function
- const max_age = 86400; // 24 hours
- const expires_at = new Date(max_age * 1000 + Date.now());
+ if (guild.widget_channel_id && !invite) {
+ // Create invite for channel if none exists
+ // TODO: Refactor invite create code to a shared function
+ const max_age = 86400; // 24 hours
+ const expires_at = new Date(max_age * 1000 + Date.now());
- invite = await Invite.create({
- code: randomString(),
- temporary: false,
- uses: 0,
- max_uses: 0,
- max_age: max_age,
- expires_at,
- created_at: new Date(),
- guild_id,
- channel_id: guild.widget_channel_id,
- flags: 0,
- }).save();
- }
+ invite = await Invite.create({
+ code: randomString(),
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ max_age: max_age,
+ expires_at,
+ created_at: new Date(),
+ guild_id,
+ channel_id: guild.widget_channel_id,
+ flags: 0,
+ }).save();
+ }
- // Fetch voice channels, and the @everyone permissions object
- const channels: { id: string; name: string; position: number }[] = [];
+ // Fetch voice channels, and the @everyone permissions object
+ const channels: { id: string; name: string; position: number }[] = [];
- (await Channel.getOrderedChannels(guild.id, guild)).filter((doc) => {
- // Only return channels where @everyone has the CONNECT permission
- if (doc.permission_overwrites === undefined || Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT) {
- channels.push({
- id: doc.id,
- name: doc.name ?? "Unknown channel",
- position: doc.position ?? 0,
- });
- }
- });
+ (await Channel.getOrderedChannels(guild.id, guild)).filter((doc) => {
+ // Only return channels where @everyone has the CONNECT permission
+ if (doc.permission_overwrites === undefined || Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT) {
+ channels.push({
+ id: doc.id,
+ name: doc.name ?? "Unknown channel",
+ position: doc.position ?? 0,
+ });
+ }
+ });
- // Fetch members
- // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
- const members = await Member.find({ where: { guild_id: guild_id } });
+ // Fetch members
+ // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
+ const members = await Member.find({ where: { guild_id: guild_id } });
- // Construct object to respond with
- const data = {
- id: guild_id,
- name: guild.name,
- instant_invite: invite?.code,
- channels: channels,
- members: members,
- presence_count: guild.presence_count,
- };
+ // Construct object to respond with
+ const data = {
+ id: guild_id,
+ name: guild.name,
+ instant_invite: invite?.code,
+ channels: channels,
+ members: members,
+ presence_count: guild.presence_count,
+ };
- res.set("Cache-Control", "public, max-age=300");
- return res.json(data);
- },
+ res.set("Cache-Control", "public, max-age=300");
+ return res.json(data);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index a7a9b60bd..b0abc95ba 100644
--- a/src/api/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -33,112 +33,112 @@ const router: Router = Router({ mergeParams: true });
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
// TODO: Cache the response
router.get(
- "/",
- route({
- responses: {
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
- // Fetch guild information
- const icon = "avatars/" + guild_id + "/" + guild.icon;
- const name = guild.name;
- const presence = guild.presence_count + " ONLINE";
+ // Fetch guild information
+ const icon = "avatars/" + guild_id + "/" + guild.icon;
+ const name = guild.name;
+ const presence = guild.presence_count + " ONLINE";
- // Fetch parameter
- const style = req.query.style?.toString() || "shield";
- if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
- throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
- }
+ // Fetch parameter
+ const style = req.query.style?.toString() || "shield";
+ if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
+ throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
+ }
- // Setup canvas
- const { createCanvas, loadImage } = require("canvas");
- const sizeOf = require("image-size");
+ // Setup canvas
+ const { createCanvas, loadImage } = require("canvas");
+ const sizeOf = require("image-size");
- // TODO: Widget style templates need Spacebar branding
- const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`);
- if (!fs.existsSync(source)) {
- throw new HTTPError("Widget template does not exist.", 400);
- }
+ // TODO: Widget style templates need Spacebar branding
+ const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`);
+ if (!fs.existsSync(source)) {
+ throw new HTTPError("Widget template does not exist.", 400);
+ }
- // Create base template image for parameter
- const { width, height } = await sizeOf(source);
- const canvas = createCanvas(width, height);
- const ctx = canvas.getContext("2d");
- const template = await loadImage(source);
- ctx.drawImage(template, 0, 0);
+ // Create base template image for parameter
+ const { width, height } = await sizeOf(source);
+ const canvas = createCanvas(width, height);
+ const ctx = canvas.getContext("2d");
+ const template = await loadImage(source);
+ ctx.drawImage(template, 0, 0);
- // Add the guild specific information to the template asset image
- switch (style) {
- case "shield":
- ctx.textAlign = "center";
- await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence);
- break;
- case "banner1":
- if (icon) await drawIcon(ctx, 20, 27, 50, icon);
- await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
- await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence);
- break;
- case "banner2":
- if (icon) await drawIcon(ctx, 13, 19, 36, icon);
- await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
- await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence);
- break;
- case "banner3":
- if (icon) await drawIcon(ctx, 20, 20, 50, icon);
- await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
- await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence);
- break;
- case "banner4":
- if (icon) await drawIcon(ctx, 21, 136, 50, icon);
- await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
- await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence);
- break;
- default:
- throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
- }
+ // Add the guild specific information to the template asset image
+ switch (style) {
+ case "shield":
+ ctx.textAlign = "center";
+ await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence);
+ break;
+ case "banner1":
+ if (icon) await drawIcon(ctx, 20, 27, 50, icon);
+ await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
+ await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence);
+ break;
+ case "banner2":
+ if (icon) await drawIcon(ctx, 13, 19, 36, icon);
+ await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
+ await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence);
+ break;
+ case "banner3":
+ if (icon) await drawIcon(ctx, 20, 20, 50, icon);
+ await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
+ await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence);
+ break;
+ case "banner4":
+ if (icon) await drawIcon(ctx, 21, 136, 50, icon);
+ await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
+ await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence);
+ break;
+ default:
+ throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
+ }
- // Return final image
- const buffer = canvas.toBuffer("image/png");
- res.set("Content-Type", "image/png");
- res.set("Cache-Control", "public, max-age=3600");
- return res.send(buffer);
- },
+ // Return final image
+ const buffer = canvas.toBuffer("image/png");
+ res.set("Content-Type", "image/png");
+ res.set("Cache-Control", "public, max-age=3600");
+ return res.send(buffer);
+ },
);
async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) {
- const { loadImage } = require("canvas");
- const img = await loadImage(await storage.get(icon));
+ const { loadImage } = require("canvas");
+ const img = await loadImage(await storage.get(icon));
- // Do some canvas clipping magic!
- canvas.save();
- canvas.beginPath();
+ // Do some canvas clipping magic!
+ canvas.save();
+ canvas.beginPath();
- const r = scale / 2; // use scale to determine radius
- canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center
+ const r = scale / 2; // use scale to determine radius
+ canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center
- canvas.clip();
- canvas.drawImage(img, x, y, scale, scale);
+ canvas.clip();
+ canvas.drawImage(img, x, y, scale, scale);
- canvas.restore();
+ canvas.restore();
}
async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) {
- canvas.fillStyle = color;
- canvas.font = font;
- if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
- canvas.fillText(text, x, y);
+ canvas.fillStyle = color;
+ canvas.font = font;
+ if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
+ canvas.fillText(text, x, y);
}
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index cad26f3d6..5a2fdb96f 100644
--- a/src/api/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -25,62 +25,62 @@ const router: Router = Router({ mergeParams: true });
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildWidgetSettingsResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildWidgetSettingsResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- return res.json({
- enabled: guild.widget_enabled || false,
- channel_id: guild.widget_channel_id || null,
- });
- },
+ 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(
- "/",
- route({
- requestBody: "WidgetModifySchema",
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "WidgetModifySchema",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as WidgetModifySchema;
- const { guild_id } = req.params;
+ "/",
+ route({
+ requestBody: "WidgetModifySchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "WidgetModifySchema",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as WidgetModifySchema;
+ const { guild_id } = req.params;
- 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
+ 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
- return res.json(body);
- },
+ return res.json(body);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/automations/email-domain-lookup.ts b/src/api/routes/guilds/automations/email-domain-lookup.ts
index 93ae231b1..605026ba6 100644
--- a/src/api/routes/guilds/automations/email-domain-lookup.ts
+++ b/src/api/routes/guilds/automations/email-domain-lookup.ts
@@ -26,74 +26,74 @@ import { EmailDomainLookupResponse, EmailDomainLookupSchema, EmailDomainLookupVe
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "EmailDomainLookupSchema",
- responses: {
- 200: {
- body: "EmailDomainLookupResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { email } = req.body as EmailDomainLookupSchema;
+ "/",
+ route({
+ requestBody: "EmailDomainLookupSchema",
+ responses: {
+ 200: {
+ body: "EmailDomainLookupResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { email } = req.body as EmailDomainLookupSchema;
- const [_, tld] = email.split("@");
+ const [_, tld] = email.split("@");
- if (emailProviders.includes(tld.toLowerCase())) {
- throw FieldErrors({
- name: {
- message: "That looks like a personal email address. Please use your official student email.",
- code: "EMAIL_IS_UNOFFICIAL",
- },
- });
- }
+ if (emailProviders.includes(tld.toLowerCase())) {
+ throw FieldErrors({
+ name: {
+ message: "That looks like a personal email address. Please use your official student email.",
+ code: "EMAIL_IS_UNOFFICIAL",
+ },
+ });
+ }
- return res.json({
- guilds_info: [],
- has_matching_guild: false,
- } as EmailDomainLookupResponse);
- },
+ return res.json({
+ guilds_info: [],
+ has_matching_guild: false,
+ } as EmailDomainLookupResponse);
+ },
);
router.post(
- "/verify-code",
- route({
- requestBody: "EmailDomainLookupVerifyCodeSchema",
- responses: {
- // 200: {
- // body: "EmailDomainLookupVerifyCodeResponse",
- // },
- 400: {
- body: "APIErrorResponse",
- },
- 501: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { email } = req.body as EmailDomainLookupVerifyCodeSchema;
+ "/verify-code",
+ route({
+ requestBody: "EmailDomainLookupVerifyCodeSchema",
+ responses: {
+ // 200: {
+ // body: "EmailDomainLookupVerifyCodeResponse",
+ // },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 501: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { email } = req.body as EmailDomainLookupVerifyCodeSchema;
- const [_, tld] = email.split("@");
+ const [_, tld] = email.split("@");
- if (emailProviders.includes(tld.toLowerCase())) {
- throw FieldErrors({
- name: {
- message: "That looks like a personal email address. Please use your official student email.",
- code: "EMAIL_IS_UNOFFICIAL",
- },
- });
- }
+ if (emailProviders.includes(tld.toLowerCase())) {
+ throw FieldErrors({
+ name: {
+ message: "That looks like a personal email address. Please use your official student email.",
+ code: "EMAIL_IS_UNOFFICIAL",
+ },
+ });
+ }
- throw new HTTPError("Not implemented", 501);
+ throw new HTTPError("Not implemented", 501);
- // return res.json({
- // guild: null,
- // joined: false,
- // } as EmailDomainLookupVerifyCodeResponse);
- },
+ // return res.json({
+ // guild: null,
+ // joined: false,
+ // } as EmailDomainLookupVerifyCodeResponse);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 6f60fa67d..78ba93a2c 100644
--- a/src/api/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -26,49 +26,49 @@ const router: Router = Router({ mergeParams: true });
//TODO: create default channel
router.post(
- "/",
- route({
- requestBody: "GuildCreateSchema",
- right: "CREATE_GUILDS",
- responses: {
- 201: {
- body: "GuildCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as GuildCreateSchema;
+ "/",
+ route({
+ requestBody: "GuildCreateSchema",
+ right: "CREATE_GUILDS",
+ responses: {
+ 201: {
+ body: "GuildCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as GuildCreateSchema;
- const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ where: { id: req.user_id } });
- const rights = await getRights(req.user_id);
- if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) {
- throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
- }
+ const { maxGuilds } = Config.get().limits.user;
+ const guild_count = await Member.count({ where: { id: req.user_id } });
+ const rights = await getRights(req.user_id);
+ if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) {
+ throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
+ }
- const guild = await Guild.createGuild({
- ...body,
- owner_id: req.user_id,
- template_guild_id: null,
- });
+ const guild = await Guild.createGuild({
+ ...body,
+ owner_id: req.user_id,
+ template_guild_id: null,
+ });
- const { autoJoin } = Config.get().guild;
- if (autoJoin.enabled && !autoJoin.guilds?.length) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
- }
+ const { autoJoin } = Config.get().guild;
+ if (autoJoin.enabled && !autoJoin.guilds?.length) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+ }
- await Member.addToGuild(req.user_id, guild.id);
+ await Member.addToGuild(req.user_id, guild.id);
- res.status(201).json(guild);
- },
+ res.status(201).json(guild);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 50ba017d8..e5dfd684c 100644
--- a/src/api/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -25,80 +25,80 @@ import { GuildTemplateCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/:template_code",
- route({
- responses: {
- 200: {
- body: "Template",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { template_code } = req.params;
+ "/:template_code",
+ route({
+ responses: {
+ 200: {
+ body: "Template",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { template_code } = req.params;
- const template = await getTemplate(template_code);
+ const template = await getTemplate(template_code);
- res.json(template);
- },
+ res.json(template);
+ },
);
router.post("/:template_code", route({ requestBody: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
- const { template_code } = req.params;
- const body = req.body as GuildTemplateCreateSchema;
+ const { template_code } = req.params;
+ const body = req.body as GuildTemplateCreateSchema;
- const { maxGuilds } = Config.get().limits.user;
+ const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ where: { id: req.user_id } });
- if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
+ const guild_count = await Member.count({ where: { id: req.user_id } });
+ if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
- const template = (await getTemplate(template_code)) as Template;
+ const template = (await getTemplate(template_code)) as Template;
- const guild = await Guild.createGuild({
- ...template.serialized_source_guild,
- // body comes after the template
- ...body,
- owner_id: req.user_id,
- template_guild_id: template.source_guild_id,
- });
+ const guild = await Guild.createGuild({
+ ...template.serialized_source_guild,
+ // body comes after the template
+ ...body,
+ owner_id: req.user_id,
+ template_guild_id: template.source_guild_id,
+ });
- await Member.addToGuild(req.user_id, guild.id);
+ await Member.addToGuild(req.user_id, guild.id);
- res.status(201).json({ id: guild.id });
+ res.status(201).json({ id: guild.id });
});
async function getTemplate(code: string) {
- const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
+ const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
- if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403);
+ if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403);
- if (code.startsWith("discord:")) {
- if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403);
+ if (code.startsWith("discord:")) {
+ if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403);
- const discordTemplateID = code.split("discord:", 2)[1];
+ const discordTemplateID = code.split("discord:", 2)[1];
- const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- });
+ const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
- return await discordTemplateData.json();
- }
+ return await discordTemplateData.json();
+ }
- if (code.startsWith("external:")) {
- if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403);
+ if (code.startsWith("external:")) {
+ if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403);
- return code.split("external:", 2)[1];
- }
+ return code.split("external:", 2)[1];
+ }
- return await Template.findOneOrFail({
- where: { code: code },
- });
+ return await Template.findOneOrFail({
+ where: { code: code },
+ });
}
export default router;
diff --git a/src/api/routes/hub-waitlist.ts b/src/api/routes/hub-waitlist.ts
index bdf093111..43c2662b0 100644
--- a/src/api/routes/hub-waitlist.ts
+++ b/src/api/routes/hub-waitlist.ts
@@ -22,28 +22,28 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/signup",
- route({
- requestBody: "HubWaitlistSignupSchema",
- responses: {
- 200: {
- body: "HubWaitlistSignupResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { email, school } = req.body as HubWaitlistSignupSchema;
+ "/signup",
+ route({
+ requestBody: "HubWaitlistSignupSchema",
+ responses: {
+ 200: {
+ body: "HubWaitlistSignupResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { email, school } = req.body as HubWaitlistSignupSchema;
- res.json({
- email,
- email_domain: email.split("@")[1],
- school,
- user_id: req.user_id,
- } as HubWaitlistSignupResponse);
- },
+ res.json({
+ email,
+ email_domain: email.split("@")[1],
+ school,
+ user_id: req.user_id,
+ } as HubWaitlistSignupResponse);
+ },
);
export default router;
diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
index 442ce0ef6..e94d455a2 100644
--- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
+++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
@@ -26,79 +26,79 @@ import { sendMessage } from "../../../../util/handlers/Message";
const router = Router({ mergeParams: true });
router.post("/", route({}), async (req: Request, res: Response) => {
- const body = req.body as InteractionCallbackSchema;
+ const body = req.body as InteractionCallbackSchema;
- const errors: Record = {};
- const knownComponentIds: string[] = [];
+ const errors: Record = {};
+ const knownComponentIds: string[] = [];
- for (const row of body.data.components || []) {
- if (!row.components) {
- continue;
- }
+ for (const row of body.data.components || []) {
+ if (!row.components) {
+ continue;
+ }
- if (row.components.length < 1 || row.components.length > 5) {
- errors[`data.components[${body.data.components!.indexOf(row)}].components`] = {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 5 in length.`,
- };
- }
+ if (row.components.length < 1 || row.components.length > 5) {
+ errors[`data.components[${body.data.components!.indexOf(row)}].components`] = {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 5 in length.`,
+ };
+ }
- for (const component of row.components) {
- if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) {
- if (component.custom_id?.trim() === "") {
- errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
- code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED",
- message: "A custom id required",
- };
- }
+ for (const component of row.components) {
+ if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) {
+ if (component.custom_id?.trim() === "") {
+ errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
+ code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED",
+ message: "A custom id required",
+ };
+ }
- if (knownComponentIds.includes(component.custom_id!)) {
- errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
- code: "COMPONENT_CUSTOM_ID_DUPLICATED",
- message: "Component custom id cannot be duplicated",
- };
- } else {
- knownComponentIds.push(component.custom_id!);
- }
- }
- }
- }
+ if (knownComponentIds.includes(component.custom_id!)) {
+ errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
+ code: "COMPONENT_CUSTOM_ID_DUPLICATED",
+ message: "Component custom id cannot be duplicated",
+ };
+ } else {
+ knownComponentIds.push(component.custom_id!);
+ }
+ }
+ }
+ }
- if (Object.keys(errors).length > 0) {
- throw FieldErrors(errors);
- }
+ if (Object.keys(errors).length > 0) {
+ throw FieldErrors(errors);
+ }
- const interactionId = req.params.interaction_id;
- const interaction = pendingInteractions.get(req.params.interaction_id);
+ const interactionId = req.params.interaction_id;
+ const interaction = pendingInteractions.get(req.params.interaction_id);
- if (!interaction) {
- return;
- }
+ if (!interaction) {
+ return;
+ }
- clearTimeout(interaction.timeout);
+ clearTimeout(interaction.timeout);
- emitEvent({
- event: "INTERACTION_SUCCESS",
- user_id: interaction?.userId,
- data: {
- id: interactionId,
- nonce: interaction?.nonce,
- },
- } as InteractionSuccessEvent);
+ emitEvent({
+ event: "INTERACTION_SUCCESS",
+ user_id: interaction?.userId,
+ data: {
+ id: interactionId,
+ nonce: interaction?.nonce,
+ },
+ } as InteractionSuccessEvent);
- switch (body.type) {
- case InteractionCallbackType.PONG:
- // TODO
- break;
- case InteractionCallbackType.ACKNOWLEDGE:
- // Deprected
- break;
- case InteractionCallbackType.CHANNEL_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: {
- const user = await User.findOneOrFail({ where: { id: interaction.userId } });
- /*
+ switch (body.type) {
+ case InteractionCallbackType.PONG:
+ // TODO
+ break;
+ case InteractionCallbackType.ACKNOWLEDGE:
+ // Deprected
+ break;
+ case InteractionCallbackType.CHANNEL_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: {
+ const user = await User.findOneOrFail({ where: { id: interaction.userId } });
+ /*
const files = (req.files as Express.Multer.File[]) ?? [];
//I don't think traditional attachments are allowed anyways
const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = [];
@@ -111,71 +111,71 @@ router.post("/", route({}), async (req: Request, res: Response) => {
}
}
*/
- await sendMessage({
- type: MessageType.APPLICATION_COMMAND,
- timestamp: new Date(),
- application_id: interaction.applicationId,
- channel_id: interaction.channelId,
- author_id: interaction.applicationId,
- nonce: interaction.nonce,
- content: body.data.content,
- components: body.data.components || [],
- tts: body.data.tts,
- embeds: body.data.embeds || [],
- attachments: body.data.attachments,
- poll: body.data.poll,
- flags: body.data.flags,
- reactions: [],
- // webhook_id: interaction.applicationId, // This one requires a webhook to be created first
- interaction: {
- id: interactionId,
- name: interaction.commandName,
- type: 2,
- user,
- },
- interaction_metadata: {
- id: interactionId,
- type: 2,
- user_id: interaction.userId,
- user,
- authorizing_integration_owners: {
- "1": interaction.userId,
- },
- name: interaction.commandName,
- command_type: interaction.commandType,
- },
- });
+ await sendMessage({
+ type: MessageType.APPLICATION_COMMAND,
+ timestamp: new Date(),
+ application_id: interaction.applicationId,
+ channel_id: interaction.channelId,
+ author_id: interaction.applicationId,
+ nonce: interaction.nonce,
+ content: body.data.content,
+ components: body.data.components || [],
+ tts: body.data.tts,
+ embeds: body.data.embeds || [],
+ attachments: body.data.attachments,
+ poll: body.data.poll,
+ flags: body.data.flags,
+ reactions: [],
+ // webhook_id: interaction.applicationId, // This one requires a webhook to be created first
+ interaction: {
+ id: interactionId,
+ name: interaction.commandName,
+ type: 2,
+ user,
+ },
+ interaction_metadata: {
+ id: interactionId,
+ type: 2,
+ user_id: interaction.userId,
+ user,
+ authorizing_integration_owners: {
+ "1": interaction.userId,
+ },
+ name: interaction.commandName,
+ command_type: interaction.commandType,
+ },
+ });
- break;
- }
- case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE:
- // TODO
- break;
- case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.UPDATE_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
- // TODO
- break;
- case InteractionCallbackType.MODAL:
- // TODO
- break;
- case InteractionCallbackType.PREMIUM_REQUIRED:
- // Deprecated
- break;
- case InteractionCallbackType.IFRAME_MODAL:
- // TODO
- break;
- case InteractionCallbackType.LAUNCH_ACTIVITY:
- // TODO
- break;
- }
+ break;
+ }
+ case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE:
+ // TODO
+ break;
+ case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.UPDATE_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
+ // TODO
+ break;
+ case InteractionCallbackType.MODAL:
+ // TODO
+ break;
+ case InteractionCallbackType.PREMIUM_REQUIRED:
+ // Deprecated
+ break;
+ case InteractionCallbackType.IFRAME_MODAL:
+ // TODO
+ break;
+ case InteractionCallbackType.LAUNCH_ACTIVITY:
+ // TODO
+ break;
+ }
- pendingInteractions.delete(interactionId);
- res.sendStatus(204);
+ pendingInteractions.delete(interactionId);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/interactions/index.ts b/src/api/routes/interactions/index.ts
index 9f1543952..eb516ced5 100644
--- a/src/api/routes/interactions/index.ts
+++ b/src/api/routes/interactions/index.ts
@@ -27,104 +27,104 @@ import { InteractionCreateSchema } from "@spacebar/schemas/api/bots/InteractionC
const router = Router({ mergeParams: true });
router.post("/", route({}), async (req: Request, res: Response) => {
- const body = req.body as InteractionSchema;
+ const body = req.body as InteractionSchema;
- const interactionId = Snowflake.generate();
- const interactionToken = randomBytes(24).toString("base64url");
+ const interactionId = Snowflake.generate();
+ const interactionToken = randomBytes(24).toString("base64url");
- emitEvent({
- event: "INTERACTION_CREATE",
- user_id: req.user_id,
- data: {
- id: interactionId,
- nonce: body.nonce,
- },
- } as InteractionCreateEvent);
+ emitEvent({
+ event: "INTERACTION_CREATE",
+ user_id: req.user_id,
+ data: {
+ id: interactionId,
+ nonce: body.nonce,
+ },
+ } as InteractionCreateEvent);
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const interactionData: Partial = {
- id: interactionId,
- application_id: body.application_id,
- channel_id: body.channel_id,
- type: body.type,
- token: interactionToken,
- version: 1,
- entitlements: [],
- authorizing_integration_owners: { "0": req.user_id },
- attachment_size_limit: Config.get().cdn.maxAttachmentSize,
- };
+ const interactionData: Partial = {
+ id: interactionId,
+ application_id: body.application_id,
+ channel_id: body.channel_id,
+ type: body.type,
+ token: interactionToken,
+ version: 1,
+ entitlements: [],
+ authorizing_integration_owners: { "0": req.user_id },
+ attachment_size_limit: Config.get().cdn.maxAttachmentSize,
+ };
- if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) {
- interactionData.data = body.data;
- }
+ if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) {
+ interactionData.data = body.data;
+ }
- if (body.type != InteractionType.Ping) {
- interactionData.locale = user?.settings?.locale;
- }
+ if (body.type != InteractionType.Ping) {
+ interactionData.locale = user?.settings?.locale;
+ }
- if (body.guild_id) {
- interactionData.context = 0;
- interactionData.guild_id = body.guild_id;
- interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString();
+ if (body.guild_id) {
+ interactionData.context = 0;
+ interactionData.guild_id = body.guild_id;
+ interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString();
- const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } });
- const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] });
+ const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } });
+ const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] });
- interactionData.guild = {
- id: guild.id,
- features: guild.features,
- locale: guild.preferred_locale!,
- };
+ interactionData.guild = {
+ id: guild.id,
+ features: guild.features,
+ locale: guild.preferred_locale!,
+ };
- interactionData.guild_locale = guild.preferred_locale;
- interactionData.member = member.toPublicMember();
- } else {
- interactionData.user = user.toPublicUser();
- interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString();
+ interactionData.guild_locale = guild.preferred_locale;
+ interactionData.member = member.toPublicMember();
+ } else {
+ interactionData.user = user.toPublicUser();
+ interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString();
- if (body.channel_id === body.application_id) {
- interactionData.context = 1;
- } else {
- interactionData.context = 2;
- }
- }
+ if (body.channel_id === body.application_id) {
+ interactionData.context = 1;
+ } else {
+ interactionData.context = 2;
+ }
+ }
- if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) {
- interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] });
- }
+ if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) {
+ interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] });
+ }
- emitEvent({
- event: "INTERACTION_CREATE",
- user_id: body.application_id,
- data: interactionData,
- } as InteractionCreateEvent);
+ emitEvent({
+ event: "INTERACTION_CREATE",
+ user_id: body.application_id,
+ data: interactionData,
+ } as InteractionCreateEvent);
- const interactionTimeout = setTimeout(() => {
- emitEvent({
- event: "INTERACTION_FAILURE",
- user_id: req.user_id,
- data: {
- id: interactionId,
- nonce: body.nonce,
- reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT,
- },
- } as InteractionFailureEvent);
- }, 3000);
+ const interactionTimeout = setTimeout(() => {
+ emitEvent({
+ event: "INTERACTION_FAILURE",
+ user_id: req.user_id,
+ data: {
+ id: interactionId,
+ nonce: body.nonce,
+ reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT,
+ },
+ } as InteractionFailureEvent);
+ }, 3000);
- pendingInteractions.set(interactionId, {
- timeout: interactionTimeout,
- nonce: body.nonce,
- applicationId: body.application_id,
- userId: req.user_id,
- guildId: body.guild_id,
- channelId: body.channel_id,
- type: body.type,
- commandType: body.data.type,
- commandName: body.data.name,
- });
+ pendingInteractions.set(interactionId, {
+ timeout: interactionTimeout,
+ nonce: body.nonce,
+ applicationId: body.application_id,
+ userId: req.user_id,
+ guildId: body.guild_id,
+ channelId: body.channel_id,
+ type: body.type,
+ commandType: body.data.type,
+ commandName: body.data.name,
+ });
- res.sendStatus(204);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts
index 87715f35e..26b0ad903 100644
--- a/src/api/routes/invites/index.ts
+++ b/src/api/routes/invites/index.ts
@@ -25,134 +25,134 @@ import { UserFlags } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/:invite_code",
- route({
- responses: {
- "200": {
- body: "Invite",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { invite_code } = req.params;
+ "/:invite_code",
+ route({
+ responses: {
+ "200": {
+ body: "Invite",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { invite_code } = req.params;
- const invite = await Invite.findOneOrFail({
- where: { code: invite_code },
- relations: PublicInviteRelation,
- });
+ const invite = await Invite.findOneOrFail({
+ where: { code: invite_code },
+ relations: PublicInviteRelation,
+ });
- res.status(200).send(invite);
- },
+ res.status(200).send(invite);
+ },
);
router.post(
- "/:invite_code",
- route({
- right: "USE_MASS_INVITES",
- responses: {
- "200": {
- body: "Invite",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
+ "/:invite_code",
+ route({
+ right: "USE_MASS_INVITES",
+ responses: {
+ "200": {
+ body: "Invite",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
- const { invite_code } = req.params;
- const { guild_id } = await Invite.findOneOrFail({
- where: { code: invite_code },
- });
- const { features } = await Guild.findOneOrFail({
- where: { id: guild_id },
- });
- const { public_flags } = await User.findOneOrFail({
- where: { id: req.user_id },
- });
- const ban = await Ban.findOne({
- where: [
- { guild_id: guild_id, user_id: req.user_id },
- { guild_id: guild_id, ip: req.ip },
- ],
- });
+ const { invite_code } = req.params;
+ const { guild_id } = await Invite.findOneOrFail({
+ where: { code: invite_code },
+ });
+ const { features } = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
+ const { public_flags } = await User.findOneOrFail({
+ where: { id: req.user_id },
+ });
+ const ban = await Ban.findOne({
+ where: [
+ { guild_id: guild_id, user_id: req.user_id },
+ { guild_id: guild_id, ip: req.ip },
+ ],
+ });
- if (ban) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`);
- throw DiscordApiErrors.USER_BANNED;
- }
+ if (ban) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`);
+ throw DiscordApiErrors.USER_BANNED;
+ }
- if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`);
- throw DiscordApiErrors.UNKNOWN_INVITE;
- }
+ if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`);
+ throw DiscordApiErrors.UNKNOWN_INVITE;
+ }
- if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`);
- throw new HTTPError("Only intended for the staff of this instance.", 401);
- }
+ if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`);
+ throw new HTTPError("Only intended for the staff of this instance.", 401);
+ }
- if (features.includes("INVITES_DISABLED")) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`);
- throw new HTTPError("Sorry, this guild has joins closed.", 403);
- }
+ if (features.includes("INVITES_DISABLED")) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`);
+ throw new HTTPError("Sorry, this guild has joins closed.", 403);
+ }
- const invite = await Invite.joinGuild(req.user_id, invite_code);
+ const invite = await Invite.joinGuild(req.user_id, invite_code);
- res.json(invite);
- },
+ res.json(invite);
+ },
);
// * cant use permission of route() function because path doesn't have guild_id/channel_id
router.delete(
- "/:invite_code",
- route({
- responses: {
- "200": {
- body: "Invite",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { invite_code } = req.params;
- const invite = await Invite.findOneOrFail({ where: { code: invite_code } });
- const { guild_id, channel_id } = invite;
+ "/:invite_code",
+ route({
+ responses: {
+ "200": {
+ body: "Invite",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { invite_code } = req.params;
+ const invite = await Invite.findOneOrFail({ where: { code: invite_code } });
+ const { guild_id, channel_id } = invite;
- const permission = await getPermission(req.user_id, guild_id, channel_id);
+ const permission = await getPermission(req.user_id, guild_id, channel_id);
- if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401);
+ 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: invite_code }),
- emitEvent({
- event: "INVITE_DELETE",
- guild_id: guild_id,
- data: {
- channel_id: channel_id,
- guild_id: guild_id,
- code: invite_code,
- },
- } as InviteDeleteEvent),
- ]);
+ await Promise.all([
+ Invite.delete({ code: invite_code }),
+ emitEvent({
+ event: "INVITE_DELETE",
+ guild_id: guild_id,
+ data: {
+ channel_id: channel_id,
+ guild_id: guild_id,
+ code: invite_code,
+ },
+ } as InviteDeleteEvent),
+ ]);
- res.json({ invite: invite });
- },
+ res.json({ invite: invite });
+ },
);
export default router;
diff --git a/src/api/routes/oauth2/applications/@me.ts b/src/api/routes/oauth2/applications/@me.ts
index 6daad7977..9cdd47c07 100644
--- a/src/api/routes/oauth2/applications/@me.ts
+++ b/src/api/routes/oauth2/applications/@me.ts
@@ -24,30 +24,30 @@ import { PublicUserProjection } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Application",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.id }, // ...huh? there's no ID in the path...
- relations: ["bot", "owner"],
- select: {
- owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
- },
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.id }, // ...huh? there's no ID in the path...
+ relations: ["bot", "owner"],
+ select: {
+ owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
+ },
+ });
- if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
+ if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
- res.json({
- ...app,
- owner: app.owner.toPublicUser(),
- install_params: app.install_params !== null ? app.install_params : undefined,
- });
- },
+ res.json({
+ ...app,
+ owner: app.owner.toPublicUser(),
+ install_params: app.install_params !== null ? app.install_params : undefined,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts
index 4084a059c..4dea75c6e 100644
--- a/src/api/routes/oauth2/authorize.ts
+++ b/src/api/routes/oauth2/authorize.ts
@@ -25,198 +25,198 @@ const router = Router({ mergeParams: true });
// TODO: scopes, other oauth types
router.get(
- "/",
- route({
- query: {
- client_id: {
- type: "string",
- },
- },
- responses: {
- // TODO: I really didn't feel like typing all of it out
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { client_id, scope, response_type, redirect_url } = req.query;
- const { client_id } = req.query;
- if (!client_id) {
- throw FieldErrors({
- client_id: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ "/",
+ route({
+ query: {
+ client_id: {
+ type: "string",
+ },
+ },
+ responses: {
+ // TODO: I really didn't feel like typing all of it out
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { client_id, scope, response_type, redirect_url } = req.query;
+ const { client_id } = req.query;
+ if (!client_id) {
+ throw FieldErrors({
+ client_id: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- const app = await Application.findOne({
- where: {
- id: client_id as string,
- },
- relations: ["bot"],
- });
+ const app = await Application.findOne({
+ where: {
+ id: client_id as string,
+ },
+ relations: ["bot"],
+ });
- // TODO: use DiscordApiErrors
- // findOneOrFail throws code 404
- if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
- if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
+ // TODO: use DiscordApiErrors
+ // findOneOrFail throws code 404
+ if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
+ if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
- const bot = app.bot;
- delete app.bot;
+ const bot = app.bot;
+ delete app.bot;
- const user = await User.findOneOrFail({
- where: {
- id: req.user_id,
- bot: false,
- },
- select: ["id", "username", "avatar", "discriminator", "public_flags"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ id: req.user_id,
+ bot: false,
+ },
+ select: ["id", "username", "avatar", "discriminator", "public_flags"],
+ });
- const guilds = await Member.find({
- where: {
- id: req.user_id,
- },
- relations: ["guild", "roles", "user"],
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- // prettier-ignore
- select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"],
- });
+ const guilds = await Member.find({
+ where: {
+ id: req.user_id,
+ },
+ relations: ["guild", "roles", "user"],
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ // prettier-ignore
+ select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"],
+ });
- const guildsWithPermissions = guilds.map((x) => {
- const perms = Permissions.finalPermission({
- user: {
- id: user.id,
- roles: x.roles?.map((x) => x.id) || [],
- communication_disabled_until: x.communication_disabled_until,
- flags: x.user.flags,
- },
- guild: {
- roles: x?.roles || [],
- id: x.guild.id,
- owner_id: x.guild.owner_id!, // ownerless guilds...?
- },
- });
+ const guildsWithPermissions = guilds.map((x) => {
+ const perms = Permissions.finalPermission({
+ user: {
+ id: user.id,
+ roles: x.roles?.map((x) => x.id) || [],
+ communication_disabled_until: x.communication_disabled_until,
+ flags: x.user.flags,
+ },
+ guild: {
+ roles: x?.roles || [],
+ id: x.guild.id,
+ owner_id: x.guild.owner_id!, // ownerless guilds...?
+ },
+ });
- return {
- id: x.guild.id,
- name: x.guild.name,
- icon: x.guild.icon,
- mfa_level: x.guild.mfa_level,
- permissions: perms.bitfield.toString(),
- };
- });
+ return {
+ id: x.guild.id,
+ name: x.guild.name,
+ icon: x.guild.icon,
+ mfa_level: x.guild.mfa_level,
+ permissions: perms.bitfield.toString(),
+ };
+ });
- return res.json({
- guilds: guildsWithPermissions,
- user: {
- id: user.id,
- username: user.username,
- avatar: user.avatar,
- avatar_decoration: null, // TODO
- discriminator: user.discriminator,
- public_flags: user.public_flags,
- },
- application: {
- id: app.id,
- name: app.name,
- icon: app.icon,
- description: app.description,
- summary: app.summary,
- type: app.type,
- hook: app.hook,
- guild_id: null, // TODO support guilds
- bot_public: app.bot_public,
- bot_require_code_grant: app.bot_require_code_grant,
- verify_key: app.verify_key,
- flags: app.flags,
- },
- bot: {
- id: bot.id,
- username: bot.username,
- avatar: bot.avatar,
- avatar_decoration: null, // TODO
- discriminator: bot.discriminator,
- public_flags: bot.public_flags,
- bot: true,
- approximated_guild_count: 0, // TODO
- },
- authorized: false,
- });
- },
+ return res.json({
+ guilds: guildsWithPermissions,
+ user: {
+ id: user.id,
+ username: user.username,
+ avatar: user.avatar,
+ avatar_decoration: null, // TODO
+ discriminator: user.discriminator,
+ public_flags: user.public_flags,
+ },
+ application: {
+ id: app.id,
+ name: app.name,
+ icon: app.icon,
+ description: app.description,
+ summary: app.summary,
+ type: app.type,
+ hook: app.hook,
+ guild_id: null, // TODO support guilds
+ bot_public: app.bot_public,
+ bot_require_code_grant: app.bot_require_code_grant,
+ verify_key: app.verify_key,
+ flags: app.flags,
+ },
+ bot: {
+ id: bot.id,
+ username: bot.username,
+ avatar: bot.avatar,
+ avatar_decoration: null, // TODO
+ discriminator: bot.discriminator,
+ public_flags: bot.public_flags,
+ bot: true,
+ approximated_guild_count: 0, // TODO
+ },
+ authorized: false,
+ });
+ },
);
router.post(
- "/",
- route({
- requestBody: "ApplicationAuthorizeSchema",
- query: {
- client_id: {
- type: "string",
- },
- },
- responses: {
- 200: {
- body: "OAuthAuthorizeResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationAuthorizeSchema;
- // const { client_id, scope, response_type, redirect_url } = req.query;
- const { client_id } = req.query;
+ "/",
+ route({
+ requestBody: "ApplicationAuthorizeSchema",
+ query: {
+ client_id: {
+ type: "string",
+ },
+ },
+ responses: {
+ 200: {
+ body: "OAuthAuthorizeResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationAuthorizeSchema;
+ // const { client_id, scope, response_type, redirect_url } = req.query;
+ const { client_id } = req.query;
- if (!client_id) {
- throw FieldErrors({
- client_id: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (!client_id) {
+ throw FieldErrors({
+ client_id: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- // TODO: ensure guild_id is not an empty string
- // TODO: captcha verification
- // TODO: MFA verification
+ // TODO: ensure guild_id is not an empty string
+ // TODO: captcha verification
+ // TODO: MFA verification
- const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
- // getPermission cache won't exist if we're owner
- if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
- perms.hasThrow("MANAGE_GUILD");
+ const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
+ // getPermission cache won't exist if we're owner
+ if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
+ perms.hasThrow("MANAGE_GUILD");
- const app = await Application.findOne({
- where: {
- id: client_id as string,
- },
- relations: ["bot"],
- });
+ const app = await Application.findOne({
+ where: {
+ id: client_id as string,
+ },
+ relations: ["bot"],
+ });
- // TODO: use DiscordApiErrors
- // findOneOrFail throws code 404
- if (!app) throw new ApiError("Unknown Application", 10002, 404);
- if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
+ // TODO: use DiscordApiErrors
+ // findOneOrFail throws code 404
+ if (!app) throw new ApiError("Unknown Application", 10002, 404);
+ if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
- await Member.addToGuild(app.id, body.guild_id);
+ await Member.addToGuild(app.id, body.guild_id);
- return res.json({
- location: "/oauth2/authorized", // redirect URL
- });
- },
+ return res.json({
+ location: "/oauth2/authorized", // redirect URL
+ });
+ },
);
export default router;
diff --git a/src/api/routes/oauth2/tokens.ts b/src/api/routes/oauth2/tokens.ts
index 6b85bab57..4af106115 100644
--- a/src/api/routes/oauth2/tokens.ts
+++ b/src/api/routes/oauth2/tokens.ts
@@ -21,8 +21,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]);
+ //TODO
+ res.json([]);
});
export default router;
diff --git a/src/api/routes/outbound-promotions.ts b/src/api/routes/outbound-promotions.ts
index f1f9a2924..e8e6f6570 100644
--- a/src/api/routes/outbound-promotions.ts
+++ b/src/api/routes/outbound-promotions.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts
index 30ec8b3f6..9a08bc6bf 100644
--- a/src/api/routes/partners/#guild_id/requirements.ts
+++ b/src/api/routes/partners/#guild_id/requirements.ts
@@ -22,34 +22,34 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
- res.send({
- guild_id: guild_id,
- safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0,
- },
- minimum_size: 0,
- });
+ res.send({
+ guild_id: guild_id,
+ safe_environment: true,
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
+ });
});
export default router;
diff --git a/src/api/routes/ping.ts b/src/api/routes/ping.ts
index 55e4b7d59..be737f840 100644
--- a/src/api/routes/ping.ts
+++ b/src/api/routes/ping.ts
@@ -23,32 +23,32 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "InstancePingResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- const { general } = Config.get();
- res.send({
- ping: "pong!",
- instance: {
- id: general.instanceId,
- name: general.instanceName,
- description: general.instanceDescription,
- image: general.image,
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstancePingResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { general } = Config.get();
+ res.send({
+ ping: "pong!",
+ instance: {
+ id: general.instanceId,
+ name: general.instanceName,
+ description: general.instanceDescription,
+ image: general.image,
- correspondenceEmail: general.correspondenceEmail,
- correspondenceUserID: general.correspondenceUserID,
+ correspondenceEmail: general.correspondenceEmail,
+ correspondenceUserID: general.correspondenceUserID,
- frontPage: general.frontPage,
- tosPage: general.tosPage,
- },
- });
- },
+ frontPage: general.frontPage,
+ tosPage: general.tosPage,
+ },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/config.ts b/src/api/routes/policies/instance/config.ts
index 97fb5093c..946ac8aa8 100755
--- a/src/api/routes/policies/instance/config.ts
+++ b/src/api/routes/policies/instance/config.ts
@@ -23,44 +23,44 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Object",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const general = Config.get();
- let outputtedConfig;
- if (req.user_id) {
- const rights = await getRights(req.user_id);
- if (rights.has("OPERATOR")) outputtedConfig = general;
- } else {
- outputtedConfig = {
- limits_user_maxGuilds: general.limits.user.maxGuilds,
- limits_user_maxBio: general.limits.user.maxBio,
- limits_guild_maxEmojis: general.limits.guild.maxEmojis,
- limits_guild_maxRoles: general.limits.guild.maxRoles,
- limits_message_maxCharacters: general.limits.message.maxCharacters,
- limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize,
- limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize,
- limits_channel_maxWebhooks: general.limits.channel.maxWebhooks,
- register_dateOfBirth_requiredc: general.register.dateOfBirth.required,
- register_password_required: general.register.password.required,
- register_disabled: general.register.disabled,
- register_requireInvite: general.register.requireInvite,
- register_allowNewRegistration: general.register.allowNewRegistration,
- register_allowMultipleAccounts: general.register.allowMultipleAccounts,
- guild_autoJoin_canLeave: general.guild.autoJoin.canLeave,
- guild_autoJoin_guilds_x: general.guild.autoJoin.guilds,
- register_email_required: general.register.email.required,
- can_recover_account: general.email.provider != null && general.general.frontPage != null,
- };
- }
- res.send(outputtedConfig);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Object",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const general = Config.get();
+ let outputtedConfig;
+ if (req.user_id) {
+ const rights = await getRights(req.user_id);
+ if (rights.has("OPERATOR")) outputtedConfig = general;
+ } else {
+ outputtedConfig = {
+ limits_user_maxGuilds: general.limits.user.maxGuilds,
+ limits_user_maxBio: general.limits.user.maxBio,
+ limits_guild_maxEmojis: general.limits.guild.maxEmojis,
+ limits_guild_maxRoles: general.limits.guild.maxRoles,
+ limits_message_maxCharacters: general.limits.message.maxCharacters,
+ limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize,
+ limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize,
+ limits_channel_maxWebhooks: general.limits.channel.maxWebhooks,
+ register_dateOfBirth_requiredc: general.register.dateOfBirth.required,
+ register_password_required: general.register.password.required,
+ register_disabled: general.register.disabled,
+ register_requireInvite: general.register.requireInvite,
+ register_allowNewRegistration: general.register.allowNewRegistration,
+ register_allowMultipleAccounts: general.register.allowMultipleAccounts,
+ guild_autoJoin_canLeave: general.guild.autoJoin.canLeave,
+ guild_autoJoin_guilds_x: general.guild.autoJoin.guilds,
+ register_email_required: general.register.email.required,
+ can_recover_account: general.email.provider != null && general.general.frontPage != null,
+ };
+ }
+ res.send(outputtedConfig);
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 967ac2f4c..7aaaeb592 100644
--- a/src/api/routes/policies/instance/domains.ts
+++ b/src/api/routes/policies/instance/domains.ts
@@ -22,26 +22,26 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "InstanceDomainsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { cdn, gateway, api } = Config.get();
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstanceDomainsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { cdn, gateway, api } = Config.get();
- res.json({
- admin: Config.get().admin.endpointPublic,
- api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client
- apiEndpoint: api.endpointPublic,
- cdn: cdn.endpointPublic,
- defaultApiVersion: api.defaultVersion,
- gateway: gateway.endpointPublic,
- });
- },
+ res.json({
+ admin: Config.get().admin.endpointPublic,
+ api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client
+ apiEndpoint: api.endpointPublic,
+ cdn: cdn.endpointPublic,
+ defaultApiVersion: api.defaultVersion,
+ gateway: gateway.endpointPublic,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index 8fc214f82..a54570f70 100644
--- a/src/api/routes/policies/instance/index.ts
+++ b/src/api/routes/policies/instance/index.ts
@@ -22,18 +22,18 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGeneralConfiguration",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { general } = Config.get();
- res.json(general);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGeneralConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { general } = Config.get();
+ res.json(general);
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 0d0ed3264..7ade15196 100644
--- a/src/api/routes/policies/instance/limits.ts
+++ b/src/api/routes/policies/instance/limits.ts
@@ -22,18 +22,18 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APILimitsConfiguration",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { limits } = Config.get();
- res.json(limits);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APILimitsConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { limits } = Config.get();
+ res.json(limits);
+ },
);
export default router;
diff --git a/src/api/routes/policies/stats.ts b/src/api/routes/policies/stats.ts
index 0828c37c2..0edcfd68f 100644
--- a/src/api/routes/policies/stats.ts
+++ b/src/api/routes/policies/stats.ts
@@ -22,32 +22,32 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "InstanceStatsResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (!Config.get().security.statsWorldReadable) {
- const rights = await getRights(req.user_id);
- rights.hasThrow("VIEW_SERVER_STATS");
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstanceStatsResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!Config.get().security.statsWorldReadable) {
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("VIEW_SERVER_STATS");
+ }
- res.json({
- counts: {
- user: await User.count(),
- guild: await Guild.count(),
- message: await Message.count(),
- members: await Member.count(),
- },
- });
- },
+ res.json({
+ counts: {
+ user: await User.count(),
+ guild: await Guild.count(),
+ message: await Message.count(),
+ members: await Member.count(),
+ },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/read-states/ack-bulk.ts b/src/api/routes/read-states/ack-bulk.ts
index a597e4bc9..d0212c0b7 100644
--- a/src/api/routes/read-states/ack-bulk.ts
+++ b/src/api/routes/read-states/ack-bulk.ts
@@ -23,48 +23,48 @@ import { AckBulkSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "AckBulkSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as AckBulkSchema;
+ "/",
+ route({
+ requestBody: "AckBulkSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as AckBulkSchema;
- // TODO: what is read_state_type ?
+ // TODO: what is read_state_type ?
- await Promise.all([
- // for every new state
- ...body.read_states.map(async (x) => {
- // find an existing one
- const ret =
- (await ReadState.findOne({
- where: {
- user_id: req.user_id,
- channel_id: x.channel_id,
- },
- })) ??
- // if it doesn't exist, create it (not a promise)
- ReadState.create({
- user_id: req.user_id,
- channel_id: x.channel_id,
- });
+ await Promise.all([
+ // for every new state
+ ...body.read_states.map(async (x) => {
+ // find an existing one
+ const ret =
+ (await ReadState.findOne({
+ where: {
+ user_id: req.user_id,
+ channel_id: x.channel_id,
+ },
+ })) ??
+ // if it doesn't exist, create it (not a promise)
+ ReadState.create({
+ user_id: req.user_id,
+ channel_id: x.channel_id,
+ });
- ret.last_message_id = x.message_id;
- //It's a little more complicated than this but this'll do
- ret.mention_count = 0;
+ ret.last_message_id = x.message_id;
+ //It's a little more complicated than this but this'll do
+ ret.mention_count = 0;
- return ret.save();
- }),
- ]);
+ return ret.save();
+ }),
+ ]);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/reporting/menu/message.ts b/src/api/routes/reporting/menu/message.ts
index c72bc840f..de57c7949 100644
--- a/src/api/routes/reporting/menu/message.ts
+++ b/src/api/routes/reporting/menu/message.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ReportingMenuResponse",
- },
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- // TODO: implement
- //res.send([] as ReportingMenuResponseSchema);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ReportingMenuResponse",
+ },
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO: implement
+ //res.send([] as ReportingMenuResponseSchema);
+ },
);
export default router;
diff --git a/src/api/routes/safety-hub/@me/index.ts b/src/api/routes/safety-hub/@me/index.ts
index 06a1e2854..7d0e7ff9b 100644
--- a/src/api/routes/safety-hub/@me/index.ts
+++ b/src/api/routes/safety-hub/@me/index.ts
@@ -24,36 +24,36 @@ import { AccountStandingResponse, AccountStandingState, AppealEligibility } from
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "AccountStandingResponse",
- },
- 401: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "AccountStandingResponse",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- res.send({
- classifications: [],
- guild_classifications: [],
- account_standing: {
- state: AccountStandingState.ALL_GOOD,
- },
- is_dsa_eligible: true,
- username: user.username,
- discriminator: user.discriminator,
- is_appeal_eligible: true,
- appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
- } as AccountStandingResponse);
- },
+ res.send({
+ classifications: [],
+ guild_classifications: [],
+ account_standing: {
+ state: AccountStandingState.ALL_GOOD,
+ },
+ is_dsa_eligible: true,
+ username: user.username,
+ discriminator: user.discriminator,
+ is_appeal_eligible: true,
+ appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
+ } as AccountStandingResponse);
+ },
);
export default router;
diff --git a/src/api/routes/safety-hub/suspended/@me.ts b/src/api/routes/safety-hub/suspended/@me.ts
index 06a1e2854..7d0e7ff9b 100644
--- a/src/api/routes/safety-hub/suspended/@me.ts
+++ b/src/api/routes/safety-hub/suspended/@me.ts
@@ -24,36 +24,36 @@ import { AccountStandingResponse, AccountStandingState, AppealEligibility } from
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "AccountStandingResponse",
- },
- 401: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "AccountStandingResponse",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- res.send({
- classifications: [],
- guild_classifications: [],
- account_standing: {
- state: AccountStandingState.ALL_GOOD,
- },
- is_dsa_eligible: true,
- username: user.username,
- discriminator: user.discriminator,
- is_appeal_eligible: true,
- appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
- } as AccountStandingResponse);
- },
+ res.send({
+ classifications: [],
+ guild_classifications: [],
+ account_standing: {
+ state: AccountStandingState.ALL_GOOD,
+ },
+ is_dsa_eligible: true,
+ username: user.username,
+ discriminator: user.discriminator,
+ is_appeal_eligible: true,
+ appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
+ } as AccountStandingResponse);
+ },
);
export default router;
diff --git a/src/api/routes/scheduled-maintenances/upcoming.json.ts b/src/api/routes/scheduled-maintenances/upcoming.json.ts
index de4dcbf14..5958d582f 100644
--- a/src/api/routes/scheduled-maintenances/upcoming.json.ts
+++ b/src/api/routes/scheduled-maintenances/upcoming.json.ts
@@ -21,10 +21,10 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json({
- page: {},
- scheduled_maintenances: {},
- });
+ res.json({
+ page: {},
+ scheduled_maintenances: {},
+ });
});
export default router;
diff --git a/src/api/routes/science.ts b/src/api/routes/science.ts
index 2d2d5195d..4a6ddeab3 100644
--- a/src/api/routes/science.ts
+++ b/src/api/routes/science.ts
@@ -22,16 +22,16 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
- },
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/stage-instances.ts b/src/api/routes/stage-instances.ts
index f1f9a2924..e8e6f6570 100644
--- a/src/api/routes/stage-instances.ts
+++ b/src/api/routes/stage-instances.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts
index 7d2fe178c..749ce0269 100644
--- a/src/api/routes/sticker-packs/index.ts
+++ b/src/api/routes/sticker-packs/index.ts
@@ -23,21 +23,21 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIStickerPackArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const sticker_packs = await StickerPack.find({
- relations: ["stickers"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIStickerPackArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const sticker_packs = await StickerPack.find({
+ relations: ["stickers"],
+ });
- res.json({ sticker_packs });
- },
+ res.json({ sticker_packs });
+ },
);
export default router;
diff --git a/src/api/routes/stickers/#sticker_id/index.ts b/src/api/routes/stickers/#sticker_id/index.ts
index ddbc3978e..0f850efdc 100644
--- a/src/api/routes/stickers/#sticker_id/index.ts
+++ b/src/api/routes/stickers/#sticker_id/index.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Sticker",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { sticker_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Sticker",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { sticker_id } = req.params;
- res.json(await Sticker.find({ where: { id: sticker_id } }));
- },
+ res.json(await Sticker.find({ where: { id: sticker_id } }));
+ },
);
export default router;
diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts
index bf4af5623..be29e0ade 100644
--- a/src/api/routes/stop.ts
+++ b/src/api/routes/stop.ts
@@ -22,21 +22,21 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- right: "OPERATOR",
- responses: {
- 200: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
- res.sendStatus(200);
- process.kill(process.pid, "SIGTERM");
- },
+ "/",
+ route({
+ right: "OPERATOR",
+ responses: {
+ 200: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
+ res.sendStatus(200);
+ process.kill(process.pid, "SIGTERM");
+ },
);
export default router;
diff --git a/src/api/routes/store/published-listings/applications/#application_id/index.ts b/src/api/routes/store/published-listings/applications/#application_id/index.ts
index eb2df64ec..39fa9db21 100644
--- a/src/api/routes/store/published-listings/applications/#application_id/index.ts
+++ b/src/api/routes/store/published-listings/applications/#application_id/index.ts
@@ -22,76 +22,76 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- // const id = req.params.id;
- res.json({
- id: "",
- summary: "",
- sku: {
- id: "",
- type: 1,
- dependent_sku_id: null,
- application_id: "",
- manifest_labels: [],
- access_type: 2,
- name: "",
- features: [],
- release_date: "",
- premium: false,
- slug: "",
- flags: 4,
- genres: [],
- legal_notice: "",
- application: {
- id: "",
- name: "",
- icon: "",
- description: "",
- summary: "",
- cover_image: "",
- primary_sku_id: "",
- hook: true,
- slug: "",
- guild_id: "",
- bot_public: "",
- bot_require_code_grant: false,
- verify_key: "",
- publishers: [
- {
- id: "",
- name: "",
- },
- ],
- developers: [
- {
- id: "",
- name: "",
- },
- ],
- system_requirements: {},
- show_age_gate: false,
- price: {
- amount: 0,
- currency: "EUR",
- },
- locales: [],
- },
- tagline: "",
- description: "",
- carousel_items: [
- {
- asset_id: "",
- },
- ],
- header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
- header_logo_light_theme: {},
- box_art: {},
- thumbnail: {},
- header_background: {},
- hero_background: {},
- assets: [],
- },
- }).status(200);
+ //TODO
+ // const id = req.params.id;
+ res.json({
+ id: "",
+ summary: "",
+ sku: {
+ id: "",
+ type: 1,
+ dependent_sku_id: null,
+ application_id: "",
+ manifest_labels: [],
+ access_type: 2,
+ name: "",
+ features: [],
+ release_date: "",
+ premium: false,
+ slug: "",
+ flags: 4,
+ genres: [],
+ legal_notice: "",
+ application: {
+ id: "",
+ name: "",
+ icon: "",
+ description: "",
+ summary: "",
+ cover_image: "",
+ primary_sku_id: "",
+ hook: true,
+ slug: "",
+ guild_id: "",
+ bot_public: "",
+ bot_require_code_grant: false,
+ verify_key: "",
+ publishers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ developers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ system_requirements: {},
+ show_age_gate: false,
+ price: {
+ amount: 0,
+ currency: "EUR",
+ },
+ locales: [],
+ },
+ tagline: "",
+ description: "",
+ carousel_items: [
+ {
+ asset_id: "",
+ },
+ ],
+ header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
+ header_logo_light_theme: {},
+ box_art: {},
+ thumbnail: {},
+ header_background: {},
+ hero_background: {},
+ assets: [],
+ },
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
index b8f5963f0..953e3919a 100644
--- a/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
@@ -22,22 +22,22 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([
- {
- id: "",
- name: "",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "",
- fallback_price: 499,
- fallback_currency: "eur",
- currency: "eur",
- price: 4199,
- price_tier: null,
- },
- ]).status(200);
+ //TODO
+ res.json([
+ {
+ id: "",
+ name: "",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "",
+ fallback_price: 499,
+ fallback_currency: "eur",
+ currency: "eur",
+ price: 4199,
+ price_tier: null,
+ },
+ ]).status(200);
});
export default router;
diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index a463b4e42..03cbab462 100644
--- a/src/api/routes/store/published-listings/skus.ts
+++ b/src/api/routes/store/published-listings/skus.ts
@@ -22,76 +22,76 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/:sku_id", route({}), async (req: Request, res: Response) => {
- //TODO
- // const id = req.params.id;
- res.json({
- id: "",
- summary: "",
- sku: {
- id: "",
- type: 1,
- dependent_sku_id: null,
- application_id: "",
- manifets_labels: [],
- access_type: 2,
- name: "",
- features: [],
- release_date: "",
- premium: false,
- slug: "",
- flags: 4,
- genres: [],
- legal_notice: "",
- application: {
- id: "",
- name: "",
- icon: "",
- description: "",
- summary: "",
- cover_image: "",
- primary_sku_id: "",
- hook: true,
- slug: "",
- guild_id: "",
- bot_public: "",
- bot_require_code_grant: false,
- verify_key: "",
- publishers: [
- {
- id: "",
- name: "",
- },
- ],
- developers: [
- {
- id: "",
- name: "",
- },
- ],
- system_requirements: {},
- show_age_gate: false,
- price: {
- amount: 0,
- currency: "EUR",
- },
- locales: [],
- },
- tagline: "",
- description: "",
- carousel_items: [
- {
- asset_id: "",
- },
- ],
- header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
- header_logo_light_theme: {},
- box_art: {},
- thumbnail: {},
- header_background: {},
- hero_background: {},
- assets: [],
- },
- }).status(200);
+ //TODO
+ // const id = req.params.id;
+ res.json({
+ id: "",
+ summary: "",
+ sku: {
+ id: "",
+ type: 1,
+ dependent_sku_id: null,
+ application_id: "",
+ manifets_labels: [],
+ access_type: 2,
+ name: "",
+ features: [],
+ release_date: "",
+ premium: false,
+ slug: "",
+ flags: 4,
+ genres: [],
+ legal_notice: "",
+ application: {
+ id: "",
+ name: "",
+ icon: "",
+ description: "",
+ summary: "",
+ cover_image: "",
+ primary_sku_id: "",
+ hook: true,
+ slug: "",
+ guild_id: "",
+ bot_public: "",
+ bot_require_code_grant: false,
+ verify_key: "",
+ publishers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ developers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ system_requirements: {},
+ show_age_gate: false,
+ price: {
+ amount: 0,
+ currency: "EUR",
+ },
+ locales: [],
+ },
+ tagline: "",
+ description: "",
+ carousel_items: [
+ {
+ asset_id: "",
+ },
+ ],
+ header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
+ header_logo_light_theme: {},
+ box_art: {},
+ thumbnail: {},
+ header_background: {},
+ hero_background: {},
+ assets: [],
+ },
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
index f2ea23cc2..0aae14950 100644
--- a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -22,310 +22,310 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
const skus = new Map([
- [
- "521842865731534868",
- [
- {
- id: "511651856145973248",
- name: "Individual Premium Tier 3 Monthly (Legacy)",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521842865731534868",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651860671627264",
- name: "Individiual Premium Tier 3 Yearly (Legacy)",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521842865731534868",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "521846918637420545",
- [
- {
- id: "511651871736201216",
- name: "Individual Premium Tier 2 Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521846918637420545",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651876987469824",
- name: "Individual Premum Tier 2 Yearly",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521846918637420545",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "978380684370378761",
- name: "Individual Premum Tier 1",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521846918637420545",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "521847234246082599",
- [
- {
- id: "642251038925127690",
- name: "Individual Premium Tier 3 Quarterly",
- interval: 1,
- interval_count: 3,
- tax_inclusive: true,
- sku_id: "521847234246082599",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651880837840896",
- name: "Individual Premium Tier 3 Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521847234246082599",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651885459963904",
- name: "Individual Premium Tier 3 Yearly",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521847234246082599",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "590663762298667008",
- [
- {
- id: "590665532894740483",
- name: "Crowd Premium Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "590663762298667008",
- discount_price: 0,
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "590665538238152709",
- name: "Crowd Premium Yearly",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "590663762298667008",
- discount_price: 0,
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "978380684370378762",
- [
- [
- {
- id: "978380692553465866",
- name: "Premium Tier 0 Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "978380684370378762",
- currency: "usd",
- price: 299,
- price_tier: null,
- prices: {
- "0": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- "3": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- "4": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- "1": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- },
- },
- ],
- ],
- ],
+ [
+ "521842865731534868",
+ [
+ {
+ id: "511651856145973248",
+ name: "Individual Premium Tier 3 Monthly (Legacy)",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521842865731534868",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651860671627264",
+ name: "Individiual Premium Tier 3 Yearly (Legacy)",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521842865731534868",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "521846918637420545",
+ [
+ {
+ id: "511651871736201216",
+ name: "Individual Premium Tier 2 Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651876987469824",
+ name: "Individual Premum Tier 2 Yearly",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "978380684370378761",
+ name: "Individual Premum Tier 1",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "521847234246082599",
+ [
+ {
+ id: "642251038925127690",
+ name: "Individual Premium Tier 3 Quarterly",
+ interval: 1,
+ interval_count: 3,
+ tax_inclusive: true,
+ sku_id: "521847234246082599",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651880837840896",
+ name: "Individual Premium Tier 3 Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521847234246082599",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651885459963904",
+ name: "Individual Premium Tier 3 Yearly",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521847234246082599",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "590663762298667008",
+ [
+ {
+ id: "590665532894740483",
+ name: "Crowd Premium Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "590663762298667008",
+ discount_price: 0,
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "590665538238152709",
+ name: "Crowd Premium Yearly",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "590663762298667008",
+ discount_price: 0,
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "978380684370378762",
+ [
+ [
+ {
+ id: "978380692553465866",
+ name: "Premium Tier 0 Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "978380684370378762",
+ currency: "usd",
+ price: 299,
+ price_tier: null,
+ prices: {
+ "0": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ "3": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ "4": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ "1": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ ],
+ ],
]);
router.get("/", route({}), async (req: Request, res: Response) => {
- // TODO: add the ability to add custom
- const { sku_id } = req.params;
+ // TODO: add the ability to add custom
+ const { sku_id } = req.params;
- if (!skus.has(sku_id)) {
- console.log(`Request for invalid SKU ${sku_id}! Please report this!`);
- res.sendStatus(404);
- } else {
- res.json(skus.get(sku_id)).status(200);
- }
+ if (!skus.has(sku_id)) {
+ console.log(`Request for invalid SKU ${sku_id}! Please report this!`);
+ res.sendStatus(404);
+ } else {
+ res.json(skus.get(sku_id)).status(200);
+ }
});
export default router;
diff --git a/src/api/routes/teams.ts b/src/api/routes/teams.ts
index 21ec3a5dc..27ccabf24 100644
--- a/src/api/routes/teams.ts
+++ b/src/api/routes/teams.ts
@@ -25,67 +25,67 @@ import { TeamCreateSchema, TeamMemberRole, TeamMemberState } from "@spacebar/sch
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- include_payout_account_status: {
- type: "boolean",
- description: "Whether to include team payout account status in the response (default false)",
- },
- },
- responses: {
- 200: {
- body: "TeamListResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const teams = await Team.find({
- where: {
- owner_user_id: req.user_id,
- },
- relations: ["members"],
- });
+ "/",
+ route({
+ query: {
+ include_payout_account_status: {
+ type: "boolean",
+ description: "Whether to include team payout account status in the response (default false)",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TeamListResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const teams = await Team.find({
+ where: {
+ owner_user_id: req.user_id,
+ },
+ relations: ["members"],
+ });
- res.send(teams);
- },
+ res.send(teams);
+ },
);
router.post(
- "/",
- route({
- requestBody: "TeamCreateSchema",
- responses: {
- 200: {
- body: "Team",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: [{ id: req.user_id }],
- select: ["mfa_enabled"],
- });
- if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team");
+ "/",
+ route({
+ requestBody: "TeamCreateSchema",
+ responses: {
+ 200: {
+ body: "Team",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: [{ id: req.user_id }],
+ select: ["mfa_enabled"],
+ });
+ if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team");
- const body = req.body as TeamCreateSchema;
+ const body = req.body as TeamCreateSchema;
- const team = Team.create({
- name: body.name,
- owner_user_id: req.user_id,
- });
- await team.save();
+ const team = Team.create({
+ name: body.name,
+ owner_user_id: req.user_id,
+ });
+ await team.save();
- await TeamMember.create({
- user_id: req.user_id,
- team_id: team.id,
- membership_state: TeamMemberState.ACCEPTED,
- permissions: ["*"],
- role: TeamMemberRole.ADMIN,
- }).save();
+ await TeamMember.create({
+ user_id: req.user_id,
+ team_id: team.id,
+ membership_state: TeamMemberState.ACCEPTED,
+ permissions: ["*"],
+ role: TeamMemberRole.ADMIN,
+ }).save();
- res.json(team);
- },
+ res.json(team);
+ },
);
export default router;
diff --git a/src/api/routes/track.ts b/src/api/routes/track.ts
index 644302cdd..d32061388 100644
--- a/src/api/routes/track.ts
+++ b/src/api/routes/track.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.post("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
+ // TODO:
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts
index 5262dc2d0..7228a761a 100644
--- a/src/api/routes/updates.ts
+++ b/src/api/routes/updates.ts
@@ -23,46 +23,46 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "UpdatesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const platform = req.query.platform;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UpdatesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const platform = req.query.platform;
- if (!platform)
- throw FieldErrors({
- platform: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
+ if (!platform)
+ throw FieldErrors({
+ platform: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
- const release = await ClientRelease.findOneOrFail({
- where: {
- enabled: true,
- platform: platform as string,
- },
- order: { pub_date: "DESC" },
- });
+ const release = await ClientRelease.findOneOrFail({
+ where: {
+ enabled: true,
+ platform: platform as string,
+ },
+ order: { pub_date: "DESC" },
+ });
- res.json({
- name: release.name,
- pub_date: release.pub_date,
- url: release.url,
- notes: release.notes,
- });
- },
+ res.json({
+ name: release.name,
+ pub_date: release.pub_date,
+ url: release.url,
+ notes: release.notes,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/#user_id/delete.ts b/src/api/routes/users/#user_id/delete.ts
index be361f189..c2286cea1 100644
--- a/src/api/routes/users/#user_id/delete.ts
+++ b/src/api/routes/users/#user_id/delete.ts
@@ -18,20 +18,20 @@
import { route } from "@spacebar/api";
import {
- Channel,
- ChannelDeleteEvent,
- ChannelRecipientRemoveEvent,
- emitEvent,
- Emoji,
- Guild,
- InstanceBan,
- Member,
- Recipient,
- Sticker,
- Stopwatch,
- User,
- UserDeleteEvent,
- UserSettingsProtos,
+ Channel,
+ ChannelDeleteEvent,
+ ChannelRecipientRemoveEvent,
+ emitEvent,
+ Emoji,
+ Guild,
+ InstanceBan,
+ Member,
+ Recipient,
+ Sticker,
+ Stopwatch,
+ User,
+ UserDeleteEvent,
+ UserSettingsProtos,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { ChannelType, InstanceUserDeleteSchema, PrivateUserProjection } from "@spacebar/schemas";
@@ -40,158 +40,158 @@ import { Not } from "typeorm";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- right: "MANAGE_USERS",
- requestBody: "InstanceUserDeleteSchema",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const sw = Stopwatch.startNew();
- const body = req.body as InstanceUserDeleteSchema | undefined;
- const user = await User.findOneOrFail({
- where: { id: req.params.user_id },
- select: [...PrivateUserProjection, "data"],
- });
+ "/",
+ route({
+ right: "MANAGE_USERS",
+ requestBody: "InstanceUserDeleteSchema",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const sw = Stopwatch.startNew();
+ const body = req.body as InstanceUserDeleteSchema | undefined;
+ const user = await User.findOneOrFail({
+ where: { id: req.params.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
- if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } })))
- await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "" }).save();
+ if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } })))
+ await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "" }).save();
- // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow
- const dmChannels = await user.getDmChannels();
- for (const channel of dmChannels) {
- console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`);
- await emitEvent({
- event: "CHANNEL_DELETE",
- data: channel.toJSON(),
- channel_id: channel.id,
- } as ChannelDeleteEvent);
- await Recipient.delete({ channel_id: channel.id });
- await Channel.deleteChannel(channel);
- }
+ // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow
+ const dmChannels = await user.getDmChannels();
+ for (const channel of dmChannels) {
+ console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`);
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel.toJSON(),
+ channel_id: channel.id,
+ } as ChannelDeleteEvent);
+ await Recipient.delete({ channel_id: channel.id });
+ await Channel.deleteChannel(channel);
+ }
- //leave all group channels
- const groupChannels = await Channel.find({
- where: { type: ChannelType.GROUP_DM },
- relations: ["recipients"],
- select: {
- id: true,
- owner_id: true,
- recipients: {
- id: true,
- user_id: true,
- },
- },
- });
+ //leave all group channels
+ const groupChannels = await Channel.find({
+ where: { type: ChannelType.GROUP_DM },
+ relations: ["recipients"],
+ select: {
+ id: true,
+ owner_id: true,
+ recipients: {
+ id: true,
+ user_id: true,
+ },
+ },
+ });
- await Promise.all(
- groupChannels.map(async (channel) => {
- const recipient = channel.recipients!.find((r) => r.user_id === user.id);
- if (recipient) {
- await Recipient.delete({ id: recipient.id });
- await emitEvent({
- event: "CHANNEL_RECIPIENT_REMOVE",
- data: {
- user: user.toPublicUser(),
- channel_id: channel.id,
- },
- channel_id: channel.id,
- } as ChannelRecipientRemoveEvent);
- console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`);
- }
+ await Promise.all(
+ groupChannels.map(async (channel) => {
+ const recipient = channel.recipients!.find((r) => r.user_id === user.id);
+ if (recipient) {
+ await Recipient.delete({ id: recipient.id });
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_REMOVE",
+ data: {
+ user: user.toPublicUser(),
+ channel_id: channel.id,
+ },
+ channel_id: channel.id,
+ } as ChannelRecipientRemoveEvent);
+ console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`);
+ }
- // if no recipients remain, delete the channel
- const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } });
- if (remainingRecipients.length === 0) {
- await emitEvent({
- event: "CHANNEL_DELETE",
- data: channel.toJSON(),
- channel_id: channel.id,
- } as ChannelDeleteEvent);
- await Channel.deleteChannel(channel);
- console.log(`[Instance ban] Deleted empty group channel ${channel.id}`);
- } else {
- // otherwise, if the banned user was the owner, reassign ownership
- if (channel.owner_id === user.id) {
- channel.owner_id = remainingRecipients[0].user_id;
- await channel.save();
- console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`);
- }
- }
- }),
- );
+ // if no recipients remain, delete the channel
+ const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } });
+ if (remainingRecipients.length === 0) {
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel.toJSON(),
+ channel_id: channel.id,
+ } as ChannelDeleteEvent);
+ await Channel.deleteChannel(channel);
+ console.log(`[Instance ban] Deleted empty group channel ${channel.id}`);
+ } else {
+ // otherwise, if the banned user was the owner, reassign ownership
+ if (channel.owner_id === user.id) {
+ channel.owner_id = remainingRecipients[0].user_id;
+ await channel.save();
+ console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`);
+ }
+ }
+ }),
+ );
- // change ownership on guilds
- const guilds = await Guild.find({ where: { owner_id: req.params.user_id } });
- await Promise.all(
- guilds.map(async (guild) => {
- const members = await Member.find({
- where: { guild_id: guild.id, id: Not(req.params.user_id) },
- relations: { roles: true },
- select: { id: true, roles: { id: true, position: true } },
- });
- const sortedMembers = members
- .filter((m) => m.id !== req.params.user_id)
- .sort((a, b) => {
- const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
- const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
- return bHighestRole.position - aHighestRole.position;
- });
- if (sortedMembers.length === 0) {
- // no members left, delete guild
- await guild.remove();
- console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`);
- } else {
- // assign new owner
- guild.owner_id = sortedMembers[0].id;
- await guild.save();
- console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`);
+ // change ownership on guilds
+ const guilds = await Guild.find({ where: { owner_id: req.params.user_id } });
+ await Promise.all(
+ guilds.map(async (guild) => {
+ const members = await Member.find({
+ where: { guild_id: guild.id, id: Not(req.params.user_id) },
+ relations: { roles: true },
+ select: { id: true, roles: { id: true, position: true } },
+ });
+ const sortedMembers = members
+ .filter((m) => m.id !== req.params.user_id)
+ .sort((a, b) => {
+ const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
+ const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
+ return bHighestRole.position - aHighestRole.position;
+ });
+ if (sortedMembers.length === 0) {
+ // no members left, delete guild
+ await guild.remove();
+ console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`);
+ } else {
+ // assign new owner
+ guild.owner_id = sortedMembers[0].id;
+ await guild.save();
+ console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`);
- // safety - reassign emojis/stickers owned by the old owner
- const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
- await Promise.all(
- stickers.map(async (sticker) => {
- sticker.user_id = guild.owner_id;
- await sticker.save();
- console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`);
- }),
- );
+ // safety - reassign emojis/stickers owned by the old owner
+ const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
+ await Promise.all(
+ stickers.map(async (sticker) => {
+ sticker.user_id = guild.owner_id;
+ await sticker.save();
+ console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`);
+ }),
+ );
- const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
- await Promise.all(
- emojis.map(async (emoji) => {
- emoji.user_id = guild.owner_id!;
- await emoji.save();
- console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`);
- }),
- );
- }
- }),
- );
+ const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
+ await Promise.all(
+ emojis.map(async (emoji) => {
+ emoji.user_id = guild.owner_id!;
+ await emoji.save();
+ console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`);
+ }),
+ );
+ }
+ }),
+ );
- const members = await Member.find({ where: { id: req.params.user_id } });
- await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
- await UserSettingsProtos.delete({ user_id: req.params.user_id });
- await User.delete({ id: req.params.user_id });
+ const members = await Member.find({ where: { id: req.params.user_id } });
+ await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
+ await UserSettingsProtos.delete({ user_id: req.params.user_id });
+ await User.delete({ id: req.params.user_id });
- // TODO: respect intents as USER_DELETE has potential to cause privacy issues
- await emitEvent({
- event: "USER_DELETE",
- user_id: req.user_id,
- data: { user_id: req.params.user_id },
- } as UserDeleteEvent);
+ // TODO: respect intents as USER_DELETE has potential to cause privacy issues
+ await emitEvent({
+ event: "USER_DELETE",
+ user_id: req.user_id,
+ data: { user_id: req.params.user_id },
+ } as UserDeleteEvent);
- console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`);
- res.sendStatus(204);
- },
+ console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`);
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/#user_id/index.ts b/src/api/routes/users/#user_id/index.ts
index 417f961d4..7fca1e0ec 100644
--- a/src/api/routes/users/#user_id/index.ts
+++ b/src/api/routes/users/#user_id/index.ts
@@ -23,19 +23,19 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIPublicUser",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPublicUser",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
- res.json(await User.getPublicUser(user_id));
- },
+ res.json(await User.getPublicUser(user_id));
+ },
);
export default router;
diff --git a/src/api/routes/users/#user_id/messages.ts b/src/api/routes/users/#user_id/messages.ts
index 717bca96d..95c7cfc4a 100644
--- a/src/api/routes/users/#user_id/messages.ts
+++ b/src/api/routes/users/#user_id/messages.ts
@@ -23,33 +23,33 @@ import { DmMessagesResponseSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "DmMessagesResponseSchema",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.params.user_id } });
- const channel = await user.getDmChannelWith(req.user_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "DmMessagesResponseSchema",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({ where: { id: req.params.user_id } });
+ const channel = await user.getDmChannelWith(req.user_id);
- const messages = (
- await Message.find({
- where: { channel_id: channel?.id },
- order: { timestamp: "DESC" },
- take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount),
- })
- ).filter((x) => x !== null) as Message[];
+ const messages = (
+ await Message.find({
+ where: { channel_id: channel?.id },
+ order: { timestamp: "DESC" },
+ take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount),
+ })
+ ).filter((x) => x !== null) as Message[];
- const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema;
+ const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema;
- return res.status(200).send(filteredMessages);
- },
+ return res.status(200).send(filteredMessages);
+ },
);
// TODO: POST to send a message to the user
diff --git a/src/api/routes/users/#user_id/profile.ts b/src/api/routes/users/#user_id/profile.ts
index a5ea3e9c4..bcd59cd18 100644
--- a/src/api/routes/users/#user_id/profile.ts
+++ b/src/api/routes/users/#user_id/profile.ts
@@ -25,146 +25,146 @@ import { PrivateUserProjection, PublicUser, PublicUserProjection, RelationshipTy
const router: Router = Router({ mergeParams: true });
router.get("/", route({ responses: { 200: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
- if (req.params.user_id === "@me") req.params.user_id = req.user_id;
+ if (req.params.user_id === "@me") req.params.user_id = req.user_id;
- const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query;
+ const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query;
- const user = await User.getPublicUser(req.params.user_id, {
- relations: ["connected_accounts"],
- });
+ const user = await User.getPublicUser(req.params.user_id, {
+ relations: ["connected_accounts"],
+ });
- const mutual_guilds: object[] = [];
- let premium_guild_since;
+ const mutual_guilds: object[] = [];
+ let premium_guild_since;
- if (with_mutual_guilds == "true") {
- const requested_member = await Member.find({
- where: { id: req.params.user_id },
- });
- const self_member = await Member.find({
- where: { id: req.user_id },
- });
+ if (with_mutual_guilds == "true") {
+ const requested_member = await Member.find({
+ where: { id: req.params.user_id },
+ });
+ const self_member = await Member.find({
+ where: { id: req.user_id },
+ });
- for (const rmem of requested_member) {
- if (rmem.premium_since) {
- if (premium_guild_since) {
- if (premium_guild_since > rmem.premium_since) {
- premium_guild_since = rmem.premium_since;
- }
- } else {
- premium_guild_since = rmem.premium_since;
- }
- }
- for (const smem of self_member) {
- if (smem.guild_id === rmem.guild_id) {
- mutual_guilds.push({
- id: rmem.guild_id,
- nick: rmem.nick,
- });
- }
- }
- }
- }
+ for (const rmem of requested_member) {
+ if (rmem.premium_since) {
+ if (premium_guild_since) {
+ if (premium_guild_since > rmem.premium_since) {
+ premium_guild_since = rmem.premium_since;
+ }
+ } else {
+ premium_guild_since = rmem.premium_since;
+ }
+ }
+ for (const smem of self_member) {
+ if (smem.guild_id === rmem.guild_id) {
+ mutual_guilds.push({
+ id: rmem.guild_id,
+ nick: rmem.nick,
+ });
+ }
+ }
+ }
+ }
- const guild_member =
- guild_id && typeof guild_id == "string"
- ? await Member.findOneOrFail({
- where: { id: req.params.user_id, guild_id: guild_id },
- relations: ["roles"],
- })
- : undefined;
+ const guild_member =
+ guild_id && typeof guild_id == "string"
+ ? await Member.findOneOrFail({
+ where: { id: req.params.user_id, guild_id: guild_id },
+ relations: ["roles"],
+ })
+ : undefined;
- // TODO: make proper DTO's in util?
+ // TODO: make proper DTO's in util?
- const userProfile = {
- bio: req.user_bot ? null : user.bio,
- accent_color: user.accent_color,
- banner: user.banner,
- pronouns: user.pronouns,
- theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers
- };
+ const userProfile = {
+ bio: req.user_bot ? null : user.bio,
+ accent_color: user.accent_color,
+ banner: user.banner,
+ pronouns: user.pronouns,
+ theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers
+ };
- const guildMemberProfile = {
- accent_color: null,
- banner: guild_member?.banner || null,
- bio: guild_member?.bio || "",
- guild_id,
- };
+ const guildMemberProfile = {
+ accent_color: null,
+ banner: guild_member?.banner || null,
+ bio: guild_member?.bio || "",
+ guild_id,
+ };
- const badges = await Badge.find();
+ const badges = await Badge.find();
- let mutual_friends: PublicUser[] = [];
- let mutual_friends_count = 0;
+ let mutual_friends: PublicUser[] = [];
+ let mutual_friends_count = 0;
- if (with_mutual_friends == "true" || with_mutual_friends_count == "true") {
- const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } });
- const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } });
- const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id));
- if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length;
- if (with_mutual_friends) {
- const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection });
- mutual_friends = users.map((u) => u.toPublicUser());
- }
- }
+ if (with_mutual_friends == "true" || with_mutual_friends_count == "true") {
+ const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } });
+ const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } });
+ const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id));
+ if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length;
+ if (with_mutual_friends) {
+ const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection });
+ mutual_friends = users.map((u) => u.toPublicUser());
+ }
+ }
- res.json({
- connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0),
- premium_guild_since: premium_guild_since, // TODO
- premium_since: user.premium_since, // TODO
- mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
- mutual_friends: with_mutual_friends ? mutual_friends : undefined,
- mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined,
- user: user.toPublicUser(),
- premium_type: user.premium_type,
- profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
- user_profile: userProfile,
- guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() },
- guild_member_profile: guild_id && guildMemberProfile,
- badges: badges.filter((x) => user.badge_ids?.includes(x.id)),
- });
+ res.json({
+ connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0),
+ premium_guild_since: premium_guild_since, // TODO
+ premium_since: user.premium_since, // TODO
+ mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
+ mutual_friends: with_mutual_friends ? mutual_friends : undefined,
+ mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined,
+ user: user.toPublicUser(),
+ premium_type: user.premium_type,
+ profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
+ user_profile: userProfile,
+ guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() },
+ guild_member_profile: guild_id && guildMemberProfile,
+ badges: badges.filter((x) => user.badge_ids?.includes(x.id)),
+ });
});
router.patch("/", route({ requestBody: "UserProfileModifySchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserProfileModifySchema;
+ const body = req.body as UserProfileModifySchema;
- if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: [...PrivateUserProjection, "data"],
- });
+ if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
- if (body.bio) {
- const { maxBio } = Config.get().limits.user;
- if (body.bio.length > maxBio) {
- throw FieldErrors({
- bio: {
- code: "BIO_INVALID",
- message: `Bio must be less than ${maxBio} in length`,
- },
- });
- }
- }
+ if (body.bio) {
+ const { maxBio } = Config.get().limits.user;
+ if (body.bio.length > maxBio) {
+ throw FieldErrors({
+ bio: {
+ code: "BIO_INVALID",
+ message: `Bio must be less than ${maxBio} in length`,
+ },
+ });
+ }
+ }
- user.assign(body);
- await user.save();
+ user.assign(body);
+ await user.save();
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- delete user.data;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ delete user.data;
- // TODO: send update member list event in gateway
- await emitEvent({
- event: "USER_UPDATE",
- user_id: req.user_id,
- data: user,
- } as UserUpdateEvent);
+ // TODO: send update member list event in gateway
+ await emitEvent({
+ event: "USER_UPDATE",
+ user_id: req.user_id,
+ data: user,
+ } as UserUpdateEvent);
- res.json({
- accent_color: user.accent_color,
- bio: user.bio,
- banner: user.banner,
- theme_colors: user.theme_colors,
- pronouns: user.pronouns,
- });
+ res.json({
+ accent_color: user.accent_color,
+ bio: user.bio,
+ banner: user.banner,
+ theme_colors: user.theme_colors,
+ pronouns: user.pronouns,
+ });
});
export default router;
diff --git a/src/api/routes/users/#user_id/relationships.ts b/src/api/routes/users/#user_id/relationships.ts
index 7e33291e1..3272ef7b4 100644
--- a/src/api/routes/users/#user_id/relationships.ts
+++ b/src/api/routes/users/#user_id/relationships.ts
@@ -24,44 +24,44 @@ import { UserRelationsResponse } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: { body: "UserRelationsResponse" },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const mutual_relations: UserRelationsResponse = [];
+ "/",
+ route({
+ responses: {
+ 200: { body: "UserRelationsResponse" },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const mutual_relations: UserRelationsResponse = [];
- const requested_relations = await User.findOneOrFail({
- where: { id: req.params.user_id },
- relations: ["relationships"],
- });
- const self_relations = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships"],
- });
+ const requested_relations = await User.findOneOrFail({
+ where: { id: req.params.user_id },
+ relations: ["relationships"],
+ });
+ const self_relations = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships"],
+ });
- for (const rmem of requested_relations.relationships) {
- for (const smem of self_relations.relationships)
- if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) {
- const relation_user = await User.getPublicUser(rmem.to_id);
+ for (const rmem of requested_relations.relationships) {
+ for (const smem of self_relations.relationships)
+ if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) {
+ const relation_user = await User.getPublicUser(rmem.to_id);
- mutual_relations.push({
- id: relation_user.id,
- username: relation_user.username,
- avatar: relation_user.avatar,
- discriminator: relation_user.discriminator,
- public_flags: relation_user.public_flags,
- });
- }
- }
+ mutual_relations.push({
+ id: relation_user.id,
+ username: relation_user.username,
+ avatar: relation_user.avatar,
+ discriminator: relation_user.discriminator,
+ public_flags: relation_user.public_flags,
+ });
+ }
+ }
- res.json(mutual_relations);
- },
+ res.json(mutual_relations);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/activities/statistics/applications.ts b/src/api/routes/users/@me/activities/statistics/applications.ts
index a9bc5961d..b01191611 100644
--- a/src/api/routes/users/@me/activities/statistics/applications.ts
+++ b/src/api/routes/users/@me/activities/statistics/applications.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json([]).status(200);
+ // TODO:
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/affinities/guilds.ts b/src/api/routes/users/@me/affinities/guilds.ts
index 30fe88795..8de9aa58b 100644
--- a/src/api/routes/users/@me/affinities/guilds.ts
+++ b/src/api/routes/users/@me/affinities/guilds.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.status(200).send({ guild_affinities: [] });
+ // TODO:
+ res.status(200).send({ guild_affinities: [] });
});
export default router;
diff --git a/src/api/routes/users/@me/affinities/users.ts b/src/api/routes/users/@me/affinities/users.ts
index 038f4108e..2f6662526 100644
--- a/src/api/routes/users/@me/affinities/users.ts
+++ b/src/api/routes/users/@me/affinities/users.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.status(200).send({ user_affinities: [], inverse_user_affinities: [] });
+ // TODO:
+ res.status(200).send({ user_affinities: [], inverse_user_affinities: [] });
});
export default router;
diff --git a/src/api/routes/users/@me/applications/#application_id/entitlements.ts b/src/api/routes/users/@me/applications/#application_id/entitlements.ts
index f1f9a2924..e8e6f6570 100644
--- a/src/api/routes/users/@me/applications/#application_id/entitlements.ts
+++ b/src/api/routes/users/@me/applications/#application_id/entitlements.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/country-code.ts b/src/api/routes/users/@me/billing/country-code.ts
index 1907536aa..8bddafc3f 100644
--- a/src/api/routes/users/@me/billing/country-code.ts
+++ b/src/api/routes/users/@me/billing/country-code.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json({ country_code: "US" }).status(200);
+ //TODO
+ res.json({ country_code: "US" }).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/location-info.ts b/src/api/routes/users/@me/billing/location-info.ts
index 3fe7f6794..918ea9139 100644
--- a/src/api/routes/users/@me/billing/location-info.ts
+++ b/src/api/routes/users/@me/billing/location-info.ts
@@ -22,9 +22,9 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- // TODO: subdivision_code (optional)
- res.json({ country_code: "US" }).status(200);
+ //TODO
+ // TODO: subdivision_code (optional)
+ res.json({ country_code: "US" }).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/payment-sources.ts b/src/api/routes/users/@me/billing/payment-sources.ts
index c2ebd3fe6..1380ed52b 100644
--- a/src/api/routes/users/@me/billing/payment-sources.ts
+++ b/src/api/routes/users/@me/billing/payment-sources.ts
@@ -23,59 +23,59 @@ const router = Router({ mergeParams: true });
// https://docs.discord.food/resources/billing#example-payment-source
const example = {
- id: "1422548914485198869",
- type: 1,
- invalid: false,
- flags: 2,
- deleted_at: null,
- brand: "visa",
- last_4: "4242",
- expires_month: 9,
- expires_year: 2077,
- billing_address: {
- name: "John Doe",
- line_1: "123 Main Street",
- line_2: "Apt 4B",
- city: "San Francisco",
- state: "CA",
- country: "US",
- postal_code: "94105",
- },
- country: "US",
- payment_gateway: 1,
- payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo",
- default: false,
+ id: "1422548914485198869",
+ type: 1,
+ invalid: false,
+ flags: 2,
+ deleted_at: null,
+ brand: "visa",
+ last_4: "4242",
+ expires_month: 9,
+ expires_year: 2077,
+ billing_address: {
+ name: "John Doe",
+ line_1: "123 Main Street",
+ line_2: "Apt 4B",
+ city: "San Francisco",
+ state: "CA",
+ country: "US",
+ postal_code: "94105",
+ },
+ country: "US",
+ payment_gateway: 1,
+ payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo",
+ default: false,
};
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json([example]).status(200);
+ // TODO: schema
+ res.json([example]).status(200);
});
router.post("/", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json([example]).status(200);
+ // TODO: schema
+ res.json([example]).status(200);
});
router.get("/:payment_source_id", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json({
- ...example,
- id: req.route.payment_source_id,
- }).status(200);
+ // TODO: schema
+ res.json({
+ ...example,
+ id: req.route.payment_source_id,
+ }).status(200);
});
router.patch("/:payment_source_id", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json({
- ...example,
- id: req.route.payment_source_id,
- }).status(200);
+ // TODO: schema
+ res.json({
+ ...example,
+ id: req.route.payment_source_id,
+ }).status(200);
});
router.delete("/:payment_source_id", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.status(204);
+ // TODO: schema
+ res.status(204);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/subscriptions.ts b/src/api/routes/users/@me/billing/subscriptions.ts
index f1f9a2924..e8e6f6570 100644
--- a/src/api/routes/users/@me/billing/subscriptions.ts
+++ b/src/api/routes/users/@me/billing/subscriptions.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index 9411e72e6..e13b874ac 100644
--- a/src/api/routes/users/@me/channels.ts
+++ b/src/api/routes/users/@me/channels.ts
@@ -24,37 +24,37 @@ import { DmChannelCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIDMChannelArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- 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]))));
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIDMChannelArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ 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]))));
+ },
);
router.post(
- "/",
- route({
- requestBody: "DmChannelCreateSchema",
- responses: {
- 200: {
- body: "DmChannelDTO",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as DmChannelCreateSchema;
- res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
- },
+ "/",
+ route({
+ requestBody: "DmChannelCreateSchema",
+ responses: {
+ 200: {
+ body: "DmChannelDTO",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as DmChannelCreateSchema;
+ res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/collectibles-marketing.ts b/src/api/routes/users/@me/collectibles-marketing.ts
index c9f1a1d1f..a043a146c 100644
--- a/src/api/routes/users/@me/collectibles-marketing.ts
+++ b/src/api/routes/users/@me/collectibles-marketing.ts
@@ -24,26 +24,26 @@ const router = Router({ mergeParams: true });
// Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "CollectiblesMarketingResponse",
- },
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.send({
- marketings: {},
- } as CollectiblesMarketingResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "CollectiblesMarketingResponse",
+ },
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.send({
+ marketings: {},
+ } as CollectiblesMarketingResponse);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/collectibles-purchases.ts b/src/api/routes/users/@me/collectibles-purchases.ts
index 9491e30ad..1e6a08d0d 100644
--- a/src/api/routes/users/@me/collectibles-purchases.ts
+++ b/src/api/routes/users/@me/collectibles-purchases.ts
@@ -23,24 +23,24 @@ const router = Router({ mergeParams: true });
// Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home
router.get(
- "/",
- route({
- responses: {
- 200: {
- // body: "CollectiblesPurchasesResponse",
- },
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.send([]);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ // body: "CollectiblesPurchasesResponse",
+ },
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.send([]);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
index fcab43219..8846efe47 100644
--- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
+++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
@@ -28,49 +28,49 @@ const ALLOWED_CONNECTIONS = ["twitch", "youtube"];
// NOTE: this route has not been extensively tested, as the required connections are not implemented as of writing
router.get("/", route({}), async (req: Request, res: Response) => {
- const { connection_name, connection_id } = req.params;
+ const { connection_name, connection_id } = req.params;
- const connection = ConnectionStore.connections.get(connection_name);
+ const connection = ConnectionStore.connections.get(connection_name);
- if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection)
- throw FieldErrors({
- provider_id: {
- code: "BASE_TYPE_CHOICES",
- message: req.t("common:field.BASE_TYPE_CHOICES", {
- types: ALLOWED_CONNECTIONS.join(", "),
- }),
- },
- });
+ if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection)
+ throw FieldErrors({
+ provider_id: {
+ code: "BASE_TYPE_CHOICES",
+ message: req.t("common:field.BASE_TYPE_CHOICES", {
+ types: ALLOWED_CONNECTIONS.join(", "),
+ }),
+ },
+ });
- if (!connection.settings.enabled)
- throw FieldErrors({
- provider_id: {
- message: "This connection has been disabled server-side.",
- },
- });
+ if (!connection.settings.enabled)
+ throw FieldErrors({
+ provider_id: {
+ message: "This connection has been disabled server-side.",
+ },
+ });
- const connectedAccount = await ConnectedAccount.findOne({
- where: {
- type: connection_name,
- external_id: connection_id,
- user_id: req.user_id,
- },
- select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
- });
- if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION;
- if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED;
- if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400);
+ const connectedAccount = await ConnectedAccount.findOne({
+ where: {
+ type: connection_name,
+ external_id: connection_id,
+ user_id: req.user_id,
+ },
+ select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
+ });
+ if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION;
+ if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED;
+ if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400);
- let access_token = connectedAccount.token_data.access_token;
- const { expires_at, expires_in, fetched_at } = connectedAccount.token_data;
+ let access_token = connectedAccount.token_data.access_token;
+ const { expires_at, expires_in, fetched_at } = connectedAccount.token_data;
- if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) {
- if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400);
- const tokenData = await connection.refresh(connectedAccount);
- access_token = tokenData.access_token;
- }
+ if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) {
+ if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400);
+ const tokenData = await connection.refresh(connectedAccount);
+ access_token = tokenData.access_token;
+ }
- res.json({ access_token });
+ res.json({ access_token });
});
export default router;
diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
index 37deee389..111312ae2 100644
--- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
+++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
@@ -24,65 +24,65 @@ const router = Router({ mergeParams: true });
// TODO: connection update schema
router.patch("/", route({ requestBody: "ConnectionUpdateSchema" }), async (req: Request, res: Response) => {
- const { connection_name, connection_id } = req.params;
- const body = req.body as ConnectionUpdateSchema;
+ const { connection_name, connection_id } = req.params;
+ const body = req.body as ConnectionUpdateSchema;
- const connection = await ConnectedAccount.findOne({
- where: {
- user_id: req.user_id,
- external_id: connection_id,
- type: connection_name,
- },
- select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"],
- });
+ const connection = await ConnectedAccount.findOne({
+ where: {
+ user_id: req.user_id,
+ external_id: connection_id,
+ type: connection_name,
+ },
+ select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"],
+ });
- if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION;
- // TODO: do we need to do anything if the connection is revoked?
+ if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION;
+ // TODO: do we need to do anything if the connection is revoked?
- if (typeof body.visibility === "boolean")
- //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
- body.visibility = body.visibility ? 1 : 0;
- if (typeof body.show_activity === "boolean")
- //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
- body.show_activity = body.show_activity ? 1 : 0;
- if (typeof body.metadata_visibility === "boolean")
- //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
- body.metadata_visibility = body.metadata_visibility ? 1 : 0;
+ if (typeof body.visibility === "boolean")
+ //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
+ body.visibility = body.visibility ? 1 : 0;
+ if (typeof body.show_activity === "boolean")
+ //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
+ body.show_activity = body.show_activity ? 1 : 0;
+ if (typeof body.metadata_visibility === "boolean")
+ //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
+ body.metadata_visibility = body.metadata_visibility ? 1 : 0;
- connection.assign(req.body);
+ connection.assign(req.body);
- await ConnectedAccount.update(
- {
- user_id: req.user_id,
- external_id: connection_id,
- type: connection_name,
- },
- connection,
- );
- res.json(connection.toJSON());
+ await ConnectedAccount.update(
+ {
+ user_id: req.user_id,
+ external_id: connection_id,
+ type: connection_name,
+ },
+ connection,
+ );
+ res.json(connection.toJSON());
});
router.delete("/", route({}), async (req: Request, res: Response) => {
- const { connection_name, connection_id } = req.params;
+ const { connection_name, connection_id } = req.params;
- const account = await ConnectedAccount.findOneOrFail({
- where: {
- user_id: req.user_id,
- external_id: connection_id,
- type: connection_name,
- },
- });
+ const account = await ConnectedAccount.findOneOrFail({
+ where: {
+ user_id: req.user_id,
+ external_id: connection_id,
+ type: connection_name,
+ },
+ });
- await Promise.all([
- ConnectedAccount.remove(account),
- emitEvent({
- event: "USER_CONNECTIONS_UPDATE",
- data: account,
- user_id: req.user_id,
- }),
- ]);
+ await Promise.all([
+ ConnectedAccount.remove(account),
+ emitEvent({
+ event: "USER_CONNECTIONS_UPDATE",
+ data: account,
+ user_id: req.user_id,
+ }),
+ ]);
- return res.sendStatus(200);
+ return res.sendStatus(200);
});
export default router;
diff --git a/src/api/routes/users/@me/connections/index.ts b/src/api/routes/users/@me/connections/index.ts
index 00625eeab..a2d8b82af 100644
--- a/src/api/routes/users/@me/connections/index.ts
+++ b/src/api/routes/users/@me/connections/index.ts
@@ -23,14 +23,14 @@ import { ConnectedAccount, ConnectedAccountDTO } from "@spacebar/util";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const connections = await ConnectedAccount.find({
- where: {
- user_id: req.user_id,
- },
- select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
- });
+ const connections = await ConnectedAccount.find({
+ where: {
+ user_id: req.user_id,
+ },
+ select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
+ });
- res.json(connections.map((x) => new ConnectedAccountDTO(x, true)));
+ res.json(connections.map((x) => new ConnectedAccountDTO(x, true)));
});
export default router;
diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index 43861181e..668f7c196 100644
--- a/src/api/routes/users/@me/delete.ts
+++ b/src/api/routes/users/@me/delete.ts
@@ -25,44 +25,44 @@ import { HTTPError } from "lambert-server";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- }); //User object
- let correctpass = true;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
+ let correctpass = true;
- if (user.data.hash) {
- // guest accounts can delete accounts without password
- correctpass = await bcrypt.compare(req.body.password, user.data.hash);
- if (!correctpass) {
- throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
- }
- }
+ if (user.data.hash) {
+ // guest accounts can delete accounts without password
+ correctpass = await bcrypt.compare(req.body.password, user.data.hash);
+ if (!correctpass) {
+ throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+ }
+ }
- // TODO: decrement guild member count
+ // TODO: decrement guild member count
- if (correctpass) {
- const members = await Member.find({ where: { id: req.user_id } });
- await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
+ if (correctpass) {
+ const members = await Member.find({ where: { id: req.user_id } });
+ await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
- res.sendStatus(204);
- } else {
- res.sendStatus(401);
- }
- },
+ res.sendStatus(204);
+ } else {
+ res.sendStatus(401);
+ }
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts
index 644302cdd..d32061388 100644
--- a/src/api/routes/users/@me/devices.ts
+++ b/src/api/routes/users/@me/devices.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.post("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
+ // TODO:
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 73de83954..95995e4bd 100644
--- a/src/api/routes/users/@me/disable.ts
+++ b/src/api/routes/users/@me/disable.ts
@@ -24,41 +24,41 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- }); //User object
- let correctpass = true;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
+ let correctpass = true;
- if (user.data.hash) {
- // guest accounts can delete accounts without password
- correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
- }
+ if (user.data.hash) {
+ // guest accounts can delete accounts without password
+ correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
+ }
- if (correctpass) {
- await User.update({ id: req.user_id }, { disabled: true });
+ if (correctpass) {
+ await User.update({ id: req.user_id }, { disabled: true });
- res.sendStatus(204);
- } else {
- res.status(400).json({
- message: "Password does not match",
- code: 50018,
- });
- }
- },
+ res.sendStatus(204);
+ } else {
+ res.status(400).json({
+ message: "Password does not match",
+ code: 50018,
+ });
+ }
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index c55750097..ede735326 100644
--- a/src/api/routes/users/@me/email-settings.ts
+++ b/src/api/routes/users/@me/email-settings.ts
@@ -22,17 +22,17 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json({
- categories: {
- social: true,
- communication: true,
- tips: false,
- updates_and_announcements: false,
- recommendations_and_events: false,
- },
- initialized: false,
- }).status(200);
+ // TODO:
+ res.json({
+ categories: {
+ social: true,
+ communication: true,
+ tips: false,
+ updates_and_announcements: false,
+ recommendations_and_events: false,
+ },
+ initialized: false,
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/entitlements.ts b/src/api/routes/users/@me/entitlements.ts
index fd953eeee..ac73a12c7 100644
--- a/src/api/routes/users/@me/entitlements.ts
+++ b/src/api/routes/users/@me/entitlements.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/gifts", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json([]).status(200);
+ // TODO:
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index df71f46eb..c06c39c48 100644
--- a/src/api/routes/users/@me/guilds.ts
+++ b/src/api/routes/users/@me/guilds.ts
@@ -24,62 +24,62 @@ import { HTTPError } from "lambert-server";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGuildArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const members = await Member.find({
- relations: ["guild"],
- where: { id: req.user_id },
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const members = await Member.find({
+ relations: ["guild"],
+ where: { id: req.user_id },
+ });
- let guild = members.map((x) => x.guild);
+ let guild = members.map((x) => x.guild);
- if ("with_counts" in req.query && req.query.with_counts == "true") {
- guild = []; // TODO: Load guilds with user role permissions number
- }
+ if ("with_counts" in req.query && req.query.with_counts == "true") {
+ guild = []; // TODO: Load guilds with user role permissions number
+ }
- res.json(guild);
- },
+ res.json(guild);
+ },
);
// user send to leave a certain guild
router.delete(
- "/:guild_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { autoJoin } = Config.get().guild;
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: ["owner_id"],
- });
+ "/:guild_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { autoJoin } = Config.get().guild;
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
- if (!guild) throw new HTTPError("Guild doesn't exist", 404);
- if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400);
- if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) {
- throw new HTTPError("You can't leave instance auto join guilds", 400);
- }
+ if (!guild) throw new HTTPError("Guild doesn't exist", 404);
+ if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400);
+ if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) {
+ throw new HTTPError("You can't leave instance auto join guilds", 400);
+ }
- await Member.removeFromGuild(req.user_id, guild_id);
+ await Member.removeFromGuild(req.user_id, guild_id);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
index 7fda9a900..16b64ec53 100644
--- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts
+++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
@@ -25,54 +25,54 @@ const router = Router({ mergeParams: true });
// GET doesn't exist on discord.com
router.get(
- "/",
- route({
- responses: {
- 200: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const user = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"],
- });
- return res.json(user.settings);
- },
+ "/",
+ route({
+ responses: {
+ 200: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: req.params.guild_id },
+ select: ["settings"],
+ });
+ return res.json(user.settings);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "UserGuildSettingsSchema",
- responses: {
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as UserGuildSettingsSchema;
+ "/",
+ route({
+ requestBody: "UserGuildSettingsSchema",
+ responses: {
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserGuildSettingsSchema;
- if (body.channel_overrides) {
- for (const channel in body.channel_overrides) {
- Channel.findOneOrFail({ where: { id: channel } });
- }
- }
+ if (body.channel_overrides) {
+ for (const channel in body.channel_overrides) {
+ Channel.findOneOrFail({ where: { id: channel } });
+ }
+ }
- const user = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"],
- });
- OrmUtils.mergeDeep(user.settings || {}, body);
- Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user);
+ const user = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: req.params.guild_id },
+ select: ["settings"],
+ });
+ OrmUtils.mergeDeep(user.settings || {}, body);
+ Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user);
- res.json(user.settings);
- },
+ res.json(user.settings);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
index a9bc5961d..b01191611 100644
--- a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
+++ b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json([]).status(200);
+ // TODO:
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index aba9f928a..bd1fa0773 100644
--- a/src/api/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -25,182 +25,182 @@ import { PrivateUserProjection, UserModifySchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIPrivateUser",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json(
- await User.findOne({
- select: PrivateUserProjection,
- where: { id: req.user_id },
- }),
- );
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPrivateUser",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json(
+ await User.findOne({
+ select: PrivateUserProjection,
+ where: { id: req.user_id },
+ }),
+ );
+ },
);
router.patch(
- "/",
- route({
- requestBody: "UserModifySchema",
- responses: {
- 200: {
- body: "UserUpdateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as UserModifySchema;
+ "/",
+ route({
+ requestBody: "UserModifySchema",
+ responses: {
+ 200: {
+ body: "UserUpdateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserModifySchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: [...PrivateUserProjection, "data"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
- // Populated on password change
- let newToken: string | undefined;
+ // Populated on password change
+ let newToken: string | undefined;
- 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);
+ 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);
- if (body.password) {
- if (user.data?.hash) {
- const same_password = await bcrypt.compare(body.password, user.data.hash || "");
- if (!same_password) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
- } else {
- user.data.hash = await bcrypt.hash(body.password, 12);
- }
- }
+ if (body.password) {
+ if (user.data?.hash) {
+ const same_password = await bcrypt.compare(body.password, user.data.hash || "");
+ if (!same_password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+ } else {
+ user.data.hash = await bcrypt.hash(body.password, 12);
+ }
+ }
- if (body.email) {
- if (!body.email && Config.get().register.email.required)
- throw FieldErrors({
- email: {
- message: req.t("auth:register.EMAIL_INVALID"),
- code: "EMAIL_INVALID",
- },
- });
- if (!body.password)
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (body.email) {
+ if (!body.email && Config.get().register.email.required)
+ throw FieldErrors({
+ email: {
+ message: req.t("auth:register.EMAIL_INVALID"),
+ code: "EMAIL_INVALID",
+ },
+ });
+ if (!body.password)
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- if (body.new_password) {
- if (!body.password && user.email) {
- throw FieldErrors({
- password: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
- user.data.hash = await bcrypt.hash(body.new_password, 12);
- user.data.valid_tokens_since = new Date();
- newToken = (await generateToken(user.id)) as string;
- }
+ if (body.new_password) {
+ if (!body.password && user.email) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
+ user.data.hash = await bcrypt.hash(body.new_password, 12);
+ user.data.valid_tokens_since = new Date();
+ newToken = (await generateToken(user.id)) as string;
+ }
- if (body.username) {
- const check_username = body?.username?.replace(/\s/g, "").trim();
- if (!check_username) {
- throw FieldErrors({
- username: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (body.username) {
+ const check_username = body?.username?.replace(/\s/g, "").trim();
+ if (!check_username) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- const { maxUsername } = Config.get().limits.user;
- if (check_username.length > maxUsername || check_username.length < 2) {
- throw FieldErrors({
- username: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 2 and ${maxUsername} in length.`,
- },
- });
- }
+ const { maxUsername } = Config.get().limits.user;
+ if (check_username.length > maxUsername || check_username.length < 2) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 2 and ${maxUsername} in length.`,
+ },
+ });
+ }
- if (!body.password) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
- }
+ if (!body.password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+ }
- if (body.discriminator) {
- if (
- await User.findOne({
- where: {
- discriminator: body.discriminator,
- username: body.username || user.username,
- },
- })
- ) {
- throw FieldErrors({
- discriminator: {
- code: "INVALID_DISCRIMINATOR",
- message: "This discriminator is already in use.",
- },
- });
- }
- }
+ if (body.discriminator) {
+ if (
+ await User.findOne({
+ where: {
+ discriminator: body.discriminator,
+ username: body.username || user.username,
+ },
+ })
+ ) {
+ throw FieldErrors({
+ discriminator: {
+ code: "INVALID_DISCRIMINATOR",
+ message: "This discriminator is already in use.",
+ },
+ });
+ }
+ }
- if (body.bio) {
- const { maxBio } = Config.get().limits.user;
- if (body.bio.length > maxBio) {
- throw FieldErrors({
- bio: {
- code: "BIO_INVALID",
- message: `Bio must be less than ${maxBio} in length`,
- },
- });
- }
- }
+ if (body.bio) {
+ const { maxBio } = Config.get().limits.user;
+ if (body.bio.length > maxBio) {
+ throw FieldErrors({
+ bio: {
+ code: "BIO_INVALID",
+ message: `Bio must be less than ${maxBio} in length`,
+ },
+ });
+ }
+ }
- user.assign(body);
- user.validate();
- await user.save();
+ user.assign(body);
+ user.validate();
+ await user.save();
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- delete user.data;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ delete user.data;
- // TODO: send update member list event in gateway
- await emitEvent({
- event: "USER_UPDATE",
- user_id: req.user_id,
- data: user,
- } as UserUpdateEvent);
+ // TODO: send update member list event in gateway
+ await emitEvent({
+ event: "USER_UPDATE",
+ user_id: req.user_id,
+ data: user,
+ } as UserUpdateEvent);
- res.json({
- ...user,
- newToken,
- });
- },
+ res.json({
+ ...user,
+ newToken,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/library.ts b/src/api/routes/users/@me/library.ts
index 845cd1712..8d09b38d3 100644
--- a/src/api/routes/users/@me/library.ts
+++ b/src/api/routes/users/@me/library.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.status(200).send([]);
+ // TODO:
+ res.status(200).send([]);
});
export default router;
diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts
index 37a7abe16..2d631e90b 100644
--- a/src/api/routes/users/@me/mentions.ts
+++ b/src/api/routes/users/@me/mentions.ts
@@ -24,139 +24,139 @@ import { In, LessThan, FindOptionsWhere } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
- "",
- route({
- responses: {
- 200: {
- body: "MessageListResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- // AFAICT this endpoint doesn't list DMs
- async (req: Request, res: Response) => {
- const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25;
- const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true;
- const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true;
- const before = req.query.before !== undefined ? String(req.query.before as string) : undefined;
- const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined;
+ "",
+ route({
+ responses: {
+ 200: {
+ body: "MessageListResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ // AFAICT this endpoint doesn't list DMs
+ async (req: Request, res: Response) => {
+ const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25;
+ const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true;
+ const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true;
+ const before = req.query.before !== undefined ? String(req.query.before as string) : undefined;
+ const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ });
- const memberships = await Member.find({
- where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) },
- select: {
- guild_id: true,
- id: true,
- communication_disabled_until: true,
- roles: {
- // We don't want to include all guild roles, as this could cause a lot more explosive behavior
- id: true,
- position: true,
- permissions: true,
- mentionable: true, // cause we can skip querying for unmentionable roles
- },
- guild: {
- id: true,
- owner_id: true,
- },
- },
- relations: ["guild", "roles"],
- });
+ const memberships = await Member.find({
+ where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) },
+ select: {
+ guild_id: true,
+ id: true,
+ communication_disabled_until: true,
+ roles: {
+ // We don't want to include all guild roles, as this could cause a lot more explosive behavior
+ id: true,
+ position: true,
+ permissions: true,
+ mentionable: true, // cause we can skip querying for unmentionable roles
+ },
+ guild: {
+ id: true,
+ owner_id: true,
+ },
+ },
+ relations: ["guild", "roles"],
+ });
- const channels = await Channel.find({
- where: {
- guild_id: In(memberships.map((m) => m.guild_id)),
- },
- select: { id: true, guild_id: true, permission_overwrites: true },
- });
+ const channels = await Channel.find({
+ where: {
+ guild_id: In(memberships.map((m) => m.guild_id)),
+ },
+ select: { id: true, guild_id: true, permission_overwrites: true },
+ });
- const visibleChannels = channels.filter((c) => {
- const member = memberships.find((m) => m.guild_id === c.guild_id)!;
- return Permissions.finalPermission({
- user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 },
- guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles },
- channel: c,
- }).has("VIEW_CHANNEL");
- });
+ const visibleChannels = channels.filter((c) => {
+ const member = memberships.find((m) => m.guild_id === c.guild_id)!;
+ return Permissions.finalPermission({
+ user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 },
+ guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles },
+ channel: c,
+ }).has("VIEW_CHANNEL");
+ });
- const visibleChannelIds = visibleChannels.map((c) => c.id);
- const ownedMentionableRoleIds = memberships.reduce((acc, m) => {
- acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id));
- return acc;
- }, [] as Snowflake[]);
+ const visibleChannelIds = visibleChannels.map((c) => c.id);
+ const ownedMentionableRoleIds = memberships.reduce((acc, m) => {
+ acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id));
+ return acc;
+ }, [] as Snowflake[]);
- const whereQuery: FindOptionsWhere[] = [
- {
- channel_id: In(visibleChannelIds),
- mentions: { id: user.id },
- id: before ? LessThan(before) : undefined,
- },
- ];
- if (everyone) {
- whereQuery.push({
- channel_id: In(visibleChannelIds),
- mention_everyone: true,
- id: before ? LessThan(before) : undefined,
- });
- }
- if (roles) {
- whereQuery.push({
- channel_id: In(visibleChannelIds),
- mention_roles: { id: In(ownedMentionableRoleIds) },
- id: before ? LessThan(before) : undefined,
- });
- }
+ const whereQuery: FindOptionsWhere[] = [
+ {
+ channel_id: In(visibleChannelIds),
+ mentions: { id: user.id },
+ id: before ? LessThan(before) : undefined,
+ },
+ ];
+ if (everyone) {
+ whereQuery.push({
+ channel_id: In(visibleChannelIds),
+ mention_everyone: true,
+ id: before ? LessThan(before) : undefined,
+ });
+ }
+ if (roles) {
+ whereQuery.push({
+ channel_id: In(visibleChannelIds),
+ mention_roles: { id: In(ownedMentionableRoleIds) },
+ id: before ? LessThan(before) : undefined,
+ });
+ }
- const sw = Stopwatch.startNew();
- const finalMessages = (
- await Message.find({
- where: whereQuery,
- order: { timestamp: "DESC" },
- relations: [
- "author",
- "webhook",
- "application",
- "mentions",
- "mention_roles",
- "mention_channels",
- "sticker_items",
- "attachments",
- "referenced_message",
- "referenced_message.author",
- "referenced_message.webhook",
- "referenced_message.application",
- "referenced_message.mentions",
- "referenced_message.mention_roles",
- "referenced_message.mention_channels",
- "referenced_message.sticker_items",
- "referenced_message.attachments",
- ],
- take: limit,
- })
- ).map((m) => {
- return {
- ...m.toJSON(),
- attachments: m.attachments?.map((attachment: Attachment) =>
- Attachment.prototype.signUrls.call(
- attachment,
- new NewUrlUserSignatureData({
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- ),
- ),
- };
- });
+ const sw = Stopwatch.startNew();
+ const finalMessages = (
+ await Message.find({
+ where: whereQuery,
+ order: { timestamp: "DESC" },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ "referenced_message",
+ "referenced_message.author",
+ "referenced_message.webhook",
+ "referenced_message.application",
+ "referenced_message.mentions",
+ "referenced_message.mention_roles",
+ "referenced_message.mention_channels",
+ "referenced_message.sticker_items",
+ "referenced_message.attachments",
+ ],
+ take: limit,
+ })
+ ).map((m) => {
+ return {
+ ...m.toJSON(),
+ attachments: m.attachments?.map((attachment: Attachment) =>
+ Attachment.prototype.signUrls.call(
+ attachment,
+ new NewUrlUserSignatureData({
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ ),
+ ),
+ };
+ });
- console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`);
+ console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`);
- return res.json(finalMessages);
- },
+ return res.json(finalMessages);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts
index c8995d834..4cc4c072e 100644
--- a/src/api/routes/users/@me/mfa/codes-verification.ts
+++ b/src/api/routes/users/@me/mfa/codes-verification.ts
@@ -24,52 +24,52 @@ import { CodesVerificationSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "CodesVerificationSchema",
- responses: {
- 200: {
- body: "APIBackupCodeArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
- const { regenerate } = req.body as CodesVerificationSchema;
+ "/",
+ route({
+ requestBody: "CodesVerificationSchema",
+ responses: {
+ 200: {
+ body: "APIBackupCodeArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
+ const { regenerate } = req.body as CodesVerificationSchema;
- // TODO: We don't have email/etc etc, so can't send a verification code.
- // Once that's done, this route can verify `key`
+ // TODO: We don't have email/etc etc, so can't send a verification code.
+ // Once that's done, this route can verify `key`
- // const user = await User.findOneOrFail({ where: { id: req.user_id } });
- if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER;
+ // const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER;
- let codes: BackupCode[];
- if (regenerate) {
- await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
+ let codes: BackupCode[];
+ if (regenerate) {
+ await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
- codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(codes.map((x) => x.save()));
- } else {
- codes = await BackupCode.find({
- where: {
- user: {
- id: req.user_id,
- },
- expired: false,
- },
- });
- }
+ codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(codes.map((x) => x.save()));
+ } else {
+ codes = await BackupCode.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ expired: false,
+ },
+ });
+ }
- return res.json({
- backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
- });
- },
+ return res.json({
+ backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts
index 579a106e3..2eb1a3e3e 100644
--- a/src/api/routes/users/@me/mfa/codes.ts
+++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -27,61 +27,61 @@ const router = Router({ mergeParams: true });
// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients
router.post(
- "/",
- route({
- requestBody: "MfaCodesSchema",
- deprecated: true,
- description: "This route is replaced with users/@me/mfa/codes-verification in newer clients",
- responses: {
- 200: {
- body: "APIBackupCodeArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { password, regenerate } = req.body as MfaCodesSchema;
+ "/",
+ route({
+ requestBody: "MfaCodesSchema",
+ deprecated: true,
+ description: "This route is replaced with users/@me/mfa/codes-verification in newer clients",
+ responses: {
+ 200: {
+ body: "APIBackupCodeArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { password, regenerate } = req.body as MfaCodesSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- if (!(await bcrypt.compare(password, user.data.hash || ""))) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- let codes: BackupCode[];
- if (regenerate) {
- await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
+ let codes: BackupCode[];
+ if (regenerate) {
+ await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
- codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(codes.map((x) => x.save()));
- } else {
- codes = await BackupCode.find({
- where: {
- user: {
- id: req.user_id,
- },
- expired: false,
- },
- });
- }
+ codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(codes.map((x) => x.save()));
+ } else {
+ codes = await BackupCode.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ expired: false,
+ },
+ });
+ }
- return res.json({
- backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
- });
- },
+ return res.json({
+ backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts
index 003dcb744..11c927e41 100644
--- a/src/api/routes/users/@me/mfa/totp/disable.ts
+++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -26,51 +26,51 @@ import { TotpDisableSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "TotpDisableSchema",
- responses: {
- 200: {
- body: "TokenOnlyResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as TotpDisableSchema;
+ "/",
+ route({
+ requestBody: "TotpDisableSchema",
+ responses: {
+ 200: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpDisableSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["totp_secret"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["totp_secret"],
+ });
- const backup = await BackupCode.findOne({ where: { code: body.code } });
- if (!backup) {
- const ret = verifyToken(user.totp_secret || "", body.code);
- if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- }
+ const backup = await BackupCode.findOne({ where: { code: body.code } });
+ if (!backup) {
+ const ret = verifyToken(user.totp_secret || "", body.code);
+ if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ }
- await User.update(
- { id: req.user_id },
- {
- mfa_enabled: false,
- totp_secret: "",
- },
- );
+ await User.update(
+ { id: req.user_id },
+ {
+ mfa_enabled: false,
+ totp_secret: "",
+ },
+ );
- await BackupCode.update(
- { user: { id: req.user_id } },
- {
- expired: true,
- },
- );
+ await BackupCode.update(
+ { user: { id: req.user_id } },
+ {
+ expired: true,
+ },
+ );
- return res.json({
- token: await generateToken(user.id),
- });
- },
+ return res.json({
+ token: await generateToken(user.id),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts
index f0563e30f..a400c6286 100644
--- a/src/api/routes/users/@me/mfa/totp/enable.ts
+++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -27,54 +27,54 @@ import { TotpEnableSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "TotpEnableSchema",
- responses: {
- 200: {
- body: "TokenWithBackupCodesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as TotpEnableSchema;
+ "/",
+ route({
+ requestBody: "TotpEnableSchema",
+ responses: {
+ 200: {
+ body: "TokenWithBackupCodesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpEnableSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data", "email"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data", "email"],
+ });
- // TODO: Are guests allowed to enable 2fa?
- if (user.data.hash) {
- if (!(await bcrypt.compare(body.password, user.data.hash))) {
- throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
- }
- }
+ // TODO: Are guests allowed to enable 2fa?
+ if (user.data.hash) {
+ if (!(await bcrypt.compare(body.password, user.data.hash))) {
+ throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+ }
+ }
- if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
+ if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
- if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- const backup_codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(backup_codes.map((x) => x.save()));
- await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret });
+ const backup_codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(backup_codes.map((x) => x.save()));
+ await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret });
- res.send({
- token: await generateToken(user.id),
- backup_codes: backup_codes.map((x) => ({
- ...x,
- expired: undefined,
- })),
- });
- },
+ res.send({
+ token: await generateToken(user.id),
+ backup_codes: backup_codes.map((x) => ({
+ ...x,
+ expired: undefined,
+ })),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
index 6dc065639..77180cac6 100644
--- a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
+++ b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
@@ -22,29 +22,29 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { key_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { key_id } = req.params;
- await SecurityKey.delete({
- id: key_id,
- user_id: req.user_id,
- });
+ await SecurityKey.delete({
+ id: key_id,
+ user_id: req.user_id,
+ });
- const keys = await SecurityKey.count({
- where: { user_id: req.user_id },
- });
+ const keys = await SecurityKey.count({
+ where: { user_id: req.user_id },
+ });
- // disable webauthn if there are no keys left
- if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false });
+ // disable webauthn if there are no keys left
+ if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false });
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
index 0c4b733db..7bd29a572 100644
--- a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
+++ b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
@@ -26,138 +26,138 @@ import { CreateWebAuthnCredentialSchema, GenerateWebAuthnCredentialsSchema, WebA
const router = Router({ mergeParams: true });
const isGenerateSchema = (body: WebAuthnPostSchema): body is GenerateWebAuthnCredentialsSchema => {
- return "password" in body;
+ return "password" in body;
};
const isCreateSchema = (body: WebAuthnPostSchema): body is CreateWebAuthnCredentialSchema => {
- return "credential" in body;
+ return "credential" in body;
};
function toArrayBuffer(buf: Buffer) {
- const ab = new ArrayBuffer(buf.length);
- const view = new Uint8Array(ab);
- for (let i = 0; i < buf.length; ++i) {
- view[i] = buf[i];
- }
- return ab;
+ const ab = new ArrayBuffer(buf.length);
+ const view = new Uint8Array(ab);
+ for (let i = 0; i < buf.length; ++i) {
+ view[i] = buf[i];
+ }
+ return ab;
}
router.get("/", route({}), async (req: Request, res: Response) => {
- const securityKeys = await SecurityKey.find({
- where: {
- user_id: req.user_id,
- },
- });
+ const securityKeys = await SecurityKey.find({
+ where: {
+ user_id: req.user_id,
+ },
+ });
- return res.json(
- securityKeys.map((key) => ({
- id: key.id,
- name: key.name,
- })),
- );
+ return res.json(
+ securityKeys.map((key) => ({
+ id: key.id,
+ name: key.name,
+ })),
+ );
});
router.post(
- "/",
- route({
- requestBody: "WebAuthnPostSchema",
- responses: {
- 200: {
- body: "WebAuthnCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (!WebAuthn.fido2) {
- // TODO: I did this for typescript and I can't use !
- throw new Error("WebAuthn not enabled");
- }
+ "/",
+ route({
+ requestBody: "WebAuthnPostSchema",
+ responses: {
+ 200: {
+ body: "WebAuthnCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!WebAuthn.fido2) {
+ // TODO: I did this for typescript and I can't use !
+ throw new Error("WebAuthn not enabled");
+ }
- const user = await User.findOneOrFail({
- where: {
- id: req.user_id,
- },
- select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"],
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ id: req.user_id,
+ },
+ select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"],
+ relations: ["settings"],
+ });
- if (isGenerateSchema(req.body)) {
- const { password } = req.body;
- 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",
- },
- });
- }
+ if (isGenerateSchema(req.body)) {
+ const { password } = req.body;
+ 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 registrationOptions = await WebAuthn.fido2.attestationOptions();
- const challenge = JSON.stringify({
- publicKey: {
- ...registrationOptions,
- challenge: Buffer.from(registrationOptions.challenge).toString("base64"),
- user: {
- id: user.id,
- name: user.username,
- displayName: user.username,
- },
- },
- });
+ const registrationOptions = await WebAuthn.fido2.attestationOptions();
+ const challenge = JSON.stringify({
+ publicKey: {
+ ...registrationOptions,
+ challenge: Buffer.from(registrationOptions.challenge).toString("base64"),
+ user: {
+ id: user.id,
+ name: user.username,
+ displayName: user.username,
+ },
+ },
+ });
- const ticket = await generateWebAuthnTicket(challenge);
+ const ticket = await generateWebAuthnTicket(challenge);
- return res.json({
- ticket: ticket,
- challenge,
- });
- } else if (isCreateSchema(req.body)) {
- const { credential, name, ticket } = req.body;
+ return res.json({
+ ticket: ticket,
+ challenge,
+ });
+ } else if (isCreateSchema(req.body)) {
+ const { credential, name, ticket } = req.body;
- const verified = await verifyWebAuthnToken(ticket);
- if (!verified) throw new HTTPError("Invalid ticket", 400);
+ const verified = await verifyWebAuthnToken(ticket);
+ if (!verified) throw new HTTPError("Invalid ticket", 400);
- const clientAttestationResponse = JSON.parse(credential);
+ const clientAttestationResponse = JSON.parse(credential);
- if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
+ if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
- const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64");
- clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer);
+ const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64");
+ clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer);
- const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
+ const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
- const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, {
- ...attestationExpectations,
- factor: "second",
- });
+ const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, {
+ ...attestationExpectations,
+ factor: "second",
+ });
- const authnrData = regResult.authnrData;
- const keyId = Buffer.from(authnrData.get("credId")).toString("base64");
- const counter = authnrData.get("counter");
- const publicKey = authnrData.get("credentialPublicKeyPem");
+ const authnrData = regResult.authnrData;
+ const keyId = Buffer.from(authnrData.get("credId")).toString("base64");
+ const counter = authnrData.get("counter");
+ const publicKey = authnrData.get("credentialPublicKeyPem");
- const securityKey = SecurityKey.create({
- name,
- counter,
- public_key: publicKey,
- user_id: req.user_id,
- key_id: keyId,
- });
+ const securityKey = SecurityKey.create({
+ name,
+ counter,
+ public_key: publicKey,
+ user_id: req.user_id,
+ key_id: keyId,
+ });
- await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]);
+ await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]);
- return res.json({
- name,
- id: securityKey.id,
- });
- } else {
- throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN;
- }
- },
+ return res.json({
+ name,
+ id: securityKey.id,
+ });
+ } else {
+ throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN;
+ }
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts
index 4969fa75f..8cd333042 100644
--- a/src/api/routes/users/@me/notes.ts
+++ b/src/api/routes/users/@me/notes.ts
@@ -23,89 +23,89 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/:user_id",
- route({
- responses: {
- 200: {
- body: "UserNoteResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
+ "/:user_id",
+ route({
+ responses: {
+ 200: {
+ body: "UserNoteResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
- const note = await Note.findOneOrFail({
- where: {
- owner: { id: req.user_id },
- target: { id: user_id },
- },
- });
+ const note = await Note.findOneOrFail({
+ where: {
+ owner: { id: req.user_id },
+ target: { id: user_id },
+ },
+ });
- return res.json({
- note: note?.content,
- note_user_id: user_id,
- user_id: req.user_id,
- });
- },
+ return res.json({
+ note: note?.content,
+ note_user_id: user_id,
+ user_id: req.user_id,
+ });
+ },
);
router.put(
- "/:user_id",
- route({
- requestBody: "UserNoteUpdateSchema",
- responses: {
- 204: {},
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
- const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw
- const { note } = req.body;
+ "/:user_id",
+ route({
+ requestBody: "UserNoteUpdateSchema",
+ responses: {
+ 204: {},
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
+ const owner = await User.findOneOrFail({ where: { id: req.user_id } });
+ const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw
+ const { note } = req.body;
- if (note && note.length) {
- // upsert a note
- if (
- await Note.findOne({
- where: {
- owner: { id: owner.id },
- target: { id: target.id },
- },
- })
- ) {
- Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note });
- } else {
- Note.insert({
- id: Snowflake.generate(),
- owner,
- target,
- content: note,
- });
- }
- } else {
- await Note.delete({
- owner: { id: owner.id },
- target: { id: target.id },
- });
- }
+ if (note && note.length) {
+ // upsert a note
+ if (
+ await Note.findOne({
+ where: {
+ owner: { id: owner.id },
+ target: { id: target.id },
+ },
+ })
+ ) {
+ Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note });
+ } else {
+ Note.insert({
+ id: Snowflake.generate(),
+ owner,
+ target,
+ content: note,
+ });
+ }
+ } else {
+ await Note.delete({
+ owner: { id: owner.id },
+ target: { id: target.id },
+ });
+ }
- await emitEvent({
- event: "USER_NOTE_UPDATE",
- data: {
- note: note,
- id: target.id,
- },
- user_id: owner.id,
- });
+ await emitEvent({
+ event: "USER_NOTE_UPDATE",
+ data: {
+ note: note,
+ id: target.id,
+ },
+ user_id: owner.id,
+ });
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index d3df9eb05..ef6c7798e 100644
--- a/src/api/routes/users/@me/relationships.ts
+++ b/src/api/routes/users/@me/relationships.ts
@@ -27,255 +27,255 @@ const router = Router({ mergeParams: true });
const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "UserRelationshipsResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships", "relationships.to"],
- select: ["id", "relationships"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UserRelationshipsResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: ["id", "relationships"],
+ });
- const related_users = user.relationships.map((r) => r.toPublicRelationship());
- return res.json(related_users);
- },
+ const related_users = user.relationships.map((r) => r.toPublicRelationship());
+ return res.json(related_users);
+ },
);
router.put(
- "/:user_id",
- route({
- requestBody: "RelationshipPutSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({
- where: { id: req.params.user_id },
- relations: ["relationships", "relationships.to"],
- select: userProjection,
- }),
- req.body.type ?? RelationshipType.friends,
- );
- },
+ "/:user_id",
+ route({
+ requestBody: "RelationshipPutSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ where: { id: req.params.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ }),
+ req.body.type ?? RelationshipType.friends,
+ );
+ },
);
router.post(
- "/",
- route({
- requestBody: "RelationshipPostSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({
- 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
- username: req.body.username,
- },
- }),
- req.body.type,
- );
- },
+ "/",
+ route({
+ requestBody: "RelationshipPostSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ 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
+ username: req.body.username,
+ },
+ }),
+ req.body.type,
+ );
+ },
);
router.delete(
- "/:user_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
- if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
+ "/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
+ if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: userProjection,
- relations: ["relationships"],
- });
- const friend = await User.findOneOrFail({
- where: { id: user_id },
- select: userProjection,
- relations: ["relationships"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
+ const friend = await User.findOneOrFail({
+ where: { id: user_id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
- const relationship = user.relationships.find((x) => x.to_id === user_id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+ const relationship = user.relationships.find((x) => x.to_id === user_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) throw new HTTPError("You are not friends with the user", 404);
- if (relationship?.type === RelationshipType.blocked) {
- // unblock user
- await Promise.all([
- Relationship.delete({ id: relationship.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- user_id: req.user_id,
- data: relationship.toPublicRelationship(),
- } as RelationshipRemoveEvent),
- ]);
- return res.sendStatus(204);
- }
- if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
- await Promise.all([
- Relationship.delete({ id: friendRequest.id }),
- await emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: friendRequest.toPublicRelationship(),
- user_id: user_id,
- } as RelationshipRemoveEvent),
- ]);
- }
+ if (relationship?.type === RelationshipType.blocked) {
+ // unblock user
+ await Promise.all([
+ Relationship.delete({ id: relationship.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ user_id: req.user_id,
+ data: relationship.toPublicRelationship(),
+ } as RelationshipRemoveEvent),
+ ]);
+ return res.sendStatus(204);
+ }
+ if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
+ await Promise.all([
+ Relationship.delete({ id: friendRequest.id }),
+ await emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: friendRequest.toPublicRelationship(),
+ user_id: user_id,
+ } as RelationshipRemoveEvent),
+ ]);
+ }
- await Promise.all([
- Relationship.delete({ id: relationship.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: relationship.toPublicRelationship(),
- user_id: req.user_id,
- } as RelationshipRemoveEvent),
- ]);
+ await Promise.all([
+ Relationship.delete({ id: relationship.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: relationship.toPublicRelationship(),
+ user_id: req.user_id,
+ } as RelationshipRemoveEvent),
+ ]);
- return res.sendStatus(204);
- },
+ 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");
+ 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({
- where: { id: req.user_id },
- relations: ["relationships", "relationships.to"],
- select: userProjection,
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ });
- let relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+ let 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 = await Relationship.create({
- to_id: id,
- type: RelationshipType.blocked,
- from_id: req.user_id,
- }).save();
- }
+ // 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 = await Relationship.create({
+ to_id: id,
+ type: RelationshipType.blocked,
+ from_id: req.user_id,
+ }).save();
+ }
- if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
- await Promise.all([
- Relationship.delete({ id: friendRequest.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: friendRequest.toPublicRelationship(),
- user_id: id,
- } as RelationshipRemoveEvent),
- ]);
- }
+ if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
+ await Promise.all([
+ Relationship.delete({ id: friendRequest.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: friendRequest.toPublicRelationship(),
+ user_id: id,
+ } as RelationshipRemoveEvent),
+ ]);
+ }
- await emitEvent({
- event: "RELATIONSHIP_ADD",
- data: relationship.toPublicRelationship(),
- 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);
- }
+ return res.sendStatus(204);
+ }
- const { maxFriends } = Config.get().limits.user;
- if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
+ const { maxFriends } = Config.get().limits.user;
+ if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
- let incoming_relationship = Relationship.create({
- nickname: undefined,
- type: RelationshipType.incoming,
- to: user,
- from: friend,
- });
- let outgoing_relationship = Relationship.create({
- nickname: undefined,
- type: RelationshipType.outgoing,
- to: friend,
- from: user,
- });
+ let incoming_relationship = Relationship.create({
+ nickname: undefined,
+ type: RelationshipType.incoming,
+ to: user,
+ from: friend,
+ });
+ let outgoing_relationship = Relationship.create({
+ 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;
- }
+ 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;
+ }
- 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");
- outgoing_relationship = relationship;
- outgoing_relationship.type = RelationshipType.friends;
- }
+ 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");
+ outgoing_relationship = relationship;
+ outgoing_relationship.type = RelationshipType.friends;
+ }
- await Promise.all([
- incoming_relationship.save(),
- outgoing_relationship.save(),
- emitEvent({
- event: "RELATIONSHIP_ADD",
- data: outgoing_relationship.toPublicRelationship(),
- user_id: req.user_id,
- } as RelationshipAddEvent),
- emitEvent({
- event: "RELATIONSHIP_ADD",
- data: {
- ...incoming_relationship.toPublicRelationship(),
- should_notify: true,
- },
- user_id: id,
- } as RelationshipAddEvent),
- ]);
+ await Promise.all([
+ incoming_relationship.save(),
+ outgoing_relationship.save(),
+ emitEvent({
+ event: "RELATIONSHIP_ADD",
+ data: outgoing_relationship.toPublicRelationship(),
+ user_id: req.user_id,
+ } as RelationshipAddEvent),
+ emitEvent({
+ event: "RELATIONSHIP_ADD",
+ data: {
+ ...incoming_relationship.toPublicRelationship(),
+ should_notify: true,
+ },
+ user_id: id,
+ } as RelationshipAddEvent),
+ ]);
- return res.sendStatus(204);
+ return res.sendStatus(204);
}
diff --git a/src/api/routes/users/@me/settings-proto/1.ts b/src/api/routes/users/@me/settings-proto/1.ts
index 6f58b422f..d54bdf361 100644
--- a/src/api/routes/users/@me/settings-proto/1.ts
+++ b/src/api/routes/users/@me/settings-proto/1.ts
@@ -27,159 +27,159 @@ const router: Router = Router({ mergeParams: true });
//#region Protobuf
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "SettingsProtoResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: PreloadedUserSettings.toBase64(userSettings.userSettings!),
- } as SettingsProtoResponse);
- },
+ res.json({
+ settings: PreloadedUserSettings.toBase64(userSettings.userSettings!),
+ } as SettingsProtoResponse);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "SettingsProtoUpdateSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
- const { atomic } = req.query;
- const updatedSettings = PreloadedUserSettings.fromBase64(settings);
+ "/",
+ route({
+ requestBody: "SettingsProtoUpdateSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
+ const { atomic } = req.query;
+ const updatedSettings = PreloadedUserSettings.fromBase64(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: PreloadedUserSettings.toBase64(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: PreloadedUserSettings.toBase64(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
//#region JSON
router.get(
- "/json",
- route({
- responses: {
- 200: {
- body: "SettingsProtoJsonResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/json",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoJsonResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: PreloadedUserSettings.toJson(userSettings.userSettings!),
- } as SettingsProtoJsonResponse);
- },
+ res.json({
+ settings: PreloadedUserSettings.toJson(userSettings.userSettings!),
+ } as SettingsProtoJsonResponse);
+ },
);
router.patch(
- "/json",
- route({
- requestBody: "SettingsProtoUpdateJsonSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateJsonResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
- const { atomic } = req.query;
- const updatedSettings = PreloadedUserSettings.fromJson(settings);
+ "/json",
+ route({
+ requestBody: "SettingsProtoUpdateJsonSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateJsonResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
+ const { atomic } = req.query;
+ const updatedSettings = PreloadedUserSettings.fromJson(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: PreloadedUserSettings.toJson(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: PreloadedUserSettings.toJson(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
async function patchUserSettings(userId: string, updatedSettings: PreloadedUserSettings, required_data_version: number | undefined, atomic: boolean = false) {
- const userSettings = await UserSettingsProtos.getOrDefault(userId);
- let settings = userSettings.userSettings!;
+ const userSettings = await UserSettingsProtos.getOrDefault(userId);
+ let settings = userSettings.userSettings!;
- if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
- return {
- settings: settings,
- out_of_date: true,
- };
- }
+ if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
+ return {
+ settings: settings,
+ out_of_date: true,
+ };
+ }
- if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false")
- console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
+ if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false")
+ console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
- if (!atomic) {
- settings = PreloadedUserSettings.fromJson(
- Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- } else {
- settings = PreloadedUserSettings.fromJson(
- OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- }
+ if (!atomic) {
+ settings = PreloadedUserSettings.fromJson(
+ Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ } else {
+ settings = PreloadedUserSettings.fromJson(
+ OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ }
- settings.versions = {
- clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
- serverVersion: settings.versions?.serverVersion ?? 0,
- dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
- };
- userSettings.userSettings = settings;
- await userSettings.save();
+ settings.versions = {
+ clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
+ serverVersion: settings.versions?.serverVersion ?? 0,
+ dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
+ };
+ userSettings.userSettings = settings;
+ await userSettings.save();
- await emitEvent({
- user_id: userId,
- event: "USER_SETTINGS_PROTO_UPDATE",
- data: {
- settings: {
- proto: PreloadedUserSettings.toBase64(settings),
- type: 1,
- },
- json_settings: {
- proto: PreloadedUserSettings.toJson(settings),
- type: "user_settings",
- },
- partial: false, // Unsure how this should behave
- },
- });
- // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
- // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
+ await emitEvent({
+ user_id: userId,
+ event: "USER_SETTINGS_PROTO_UPDATE",
+ data: {
+ settings: {
+ proto: PreloadedUserSettings.toBase64(settings),
+ type: 1,
+ },
+ json_settings: {
+ proto: PreloadedUserSettings.toJson(settings),
+ type: "user_settings",
+ },
+ partial: false, // Unsure how this should behave
+ },
+ });
+ // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
+ // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
- return {
- settings: settings,
- out_of_date: false,
- };
+ return {
+ settings: settings,
+ out_of_date: false,
+ };
}
export default router;
diff --git a/src/api/routes/users/@me/settings-proto/2.ts b/src/api/routes/users/@me/settings-proto/2.ts
index b37805fdb..809db8487 100644
--- a/src/api/routes/users/@me/settings-proto/2.ts
+++ b/src/api/routes/users/@me/settings-proto/2.ts
@@ -27,159 +27,159 @@ const router: Router = Router({ mergeParams: true });
//#region Protobuf
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "SettingsProtoResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!),
- } as SettingsProtoResponse);
- },
+ res.json({
+ settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!),
+ } as SettingsProtoResponse);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "SettingsProtoUpdateSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
- const { atomic } = req.query;
- const updatedSettings = FrecencyUserSettings.fromBase64(settings);
+ "/",
+ route({
+ requestBody: "SettingsProtoUpdateSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
+ const { atomic } = req.query;
+ const updatedSettings = FrecencyUserSettings.fromBase64(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: FrecencyUserSettings.toBase64(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: FrecencyUserSettings.toBase64(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
//#region JSON
router.get(
- "/json",
- route({
- responses: {
- 200: {
- body: "SettingsProtoJsonResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/json",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoJsonResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!),
- } as SettingsProtoJsonResponse);
- },
+ res.json({
+ settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!),
+ } as SettingsProtoJsonResponse);
+ },
);
router.patch(
- "/json",
- route({
- requestBody: "SettingsProtoUpdateJsonSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateJsonResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
- const { atomic } = req.query;
- const updatedSettings = FrecencyUserSettings.fromJson(settings);
+ "/json",
+ route({
+ requestBody: "SettingsProtoUpdateJsonSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateJsonResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
+ const { atomic } = req.query;
+ const updatedSettings = FrecencyUserSettings.fromJson(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: FrecencyUserSettings.toJson(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: FrecencyUserSettings.toJson(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
async function patchUserSettings(userId: string, updatedSettings: FrecencyUserSettings, required_data_version: number | undefined, atomic: boolean = false) {
- const userSettings = await UserSettingsProtos.getOrDefault(userId);
- let settings = userSettings.frecencySettings!;
+ const userSettings = await UserSettingsProtos.getOrDefault(userId);
+ let settings = userSettings.frecencySettings!;
- if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
- return {
- settings: settings,
- out_of_date: true,
- };
- }
+ if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
+ return {
+ settings: settings,
+ out_of_date: true,
+ };
+ }
- if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false")
- console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
+ if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false")
+ console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
- if (!atomic) {
- settings = FrecencyUserSettings.fromJson(
- Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- } else {
- settings = FrecencyUserSettings.fromJson(
- OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- }
+ if (!atomic) {
+ settings = FrecencyUserSettings.fromJson(
+ Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ } else {
+ settings = FrecencyUserSettings.fromJson(
+ OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ }
- settings.versions = {
- clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
- serverVersion: settings.versions?.serverVersion ?? 0,
- dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
- };
- userSettings.frecencySettings = settings;
- await userSettings.save();
+ settings.versions = {
+ clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
+ serverVersion: settings.versions?.serverVersion ?? 0,
+ dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
+ };
+ userSettings.frecencySettings = settings;
+ await userSettings.save();
- await emitEvent({
- user_id: userId,
- event: "USER_SETTINGS_PROTO_UPDATE",
- data: {
- settings: {
- proto: FrecencyUserSettings.toBase64(settings),
- type: 2,
- },
- json_settings: {
- proto: FrecencyUserSettings.toJson(settings),
- type: "frecency_settings",
- },
- partial: false, // Unsure how this should behave
- },
- });
- // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
- // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
+ await emitEvent({
+ user_id: userId,
+ event: "USER_SETTINGS_PROTO_UPDATE",
+ data: {
+ settings: {
+ proto: FrecencyUserSettings.toBase64(settings),
+ type: 2,
+ },
+ json_settings: {
+ proto: FrecencyUserSettings.toJson(settings),
+ type: "frecency_settings",
+ },
+ partial: false, // Unsure how this should behave
+ },
+ });
+ // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
+ // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
- return {
- settings: settings,
- out_of_date: false,
- };
+ return {
+ settings: settings,
+ out_of_date: false,
+ };
}
export default router;
diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index d220b859d..57ffba14e 100644
--- a/src/api/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -24,81 +24,81 @@ import { UserSettingsUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "UserSettings",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const settings = await UserSettings.getOrDefault(req.user_id);
- return res.json(settings);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UserSettings",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const settings = await UserSettings.getOrDefault(req.user_id);
+ return res.json(settings);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "UserSettingsUpdateSchema",
- responses: {
- 200: {
- body: "UserSettings",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as UserSettingsUpdateSchema;
- if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" });
- if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale
+ "/",
+ route({
+ requestBody: "UserSettingsUpdateSchema",
+ responses: {
+ 200: {
+ body: "UserSettings",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserSettingsUpdateSchema;
+ if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" });
+ if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale
- const user = await User.findOneOrFail({
- where: { id: req.user_id, bot: false },
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id, bot: false },
+ relations: ["settings"],
+ });
- if (!user.settings) user.settings = UserSettings.create(body);
- else user.settings.assign(body);
+ if (!user.settings) user.settings = UserSettings.create(body);
+ else user.settings.assign(body);
- if (body.guild_folders) user.settings.guild_folders = body.guild_folders;
+ if (body.guild_folders) user.settings.guild_folders = body.guild_folders;
- await user.settings.save();
- await user.save();
- if (body.status) {
- const [session] = (await Session.find({
- where: { user_id: user.id },
- })) as [Session | undefined];
- if (session) {
- session.status = body.status;
+ await user.settings.save();
+ await user.save();
+ if (body.status) {
+ const [session] = (await Session.find({
+ where: { user_id: user.id },
+ })) as [Session | undefined];
+ if (session) {
+ session.status = body.status;
- await Promise.all([
- emitEvent({
- event: "PRESENCE_UPDATE",
- user_id: user.id,
- data: {
- user: user.toPublicUser(),
- activities: session.activities,
- client_status: session?.client_status,
- status: session.getPublicStatus(),
- },
- } as PresenceUpdateEvent),
- session.save(),
- ]);
- }
- }
+ await Promise.all([
+ emitEvent({
+ event: "PRESENCE_UPDATE",
+ user_id: user.id,
+ data: {
+ user: user.toPublicUser(),
+ activities: session.activities,
+ client_status: session?.client_status,
+ status: session.getPublicStatus(),
+ },
+ } as PresenceUpdateEvent),
+ session.save(),
+ ]);
+ }
+ }
- res.json({ ...user.settings, index: undefined });
- },
+ res.json({ ...user.settings, index: undefined });
+ },
);
export default router;
diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts
index ba2612316..a6759b553 100644
--- a/src/api/routes/voice/regions.ts
+++ b/src/api/routes/voice/regions.ts
@@ -22,17 +22,17 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGuildVoiceRegion",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json(await getVoiceRegions(req.ip!, true)); //vip true?
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildVoiceRegion",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json(await getVoiceRegions(req.ip!, true)); //vip true?
+ },
);
export default router;
diff --git a/src/api/routes/webhooks/#webhook_id/#token/github.ts b/src/api/routes/webhooks/#webhook_id/#token/github.ts
index 78d6bbb8e..81aa40a93 100644
--- a/src/api/routes/webhooks/#webhook_id/#token/github.ts
+++ b/src/api/routes/webhooks/#webhook_id/#token/github.ts
@@ -7,402 +7,402 @@ import { WebhookExecuteSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
const parseGitHubWebhook = (req: Request, res: Response, next: NextFunction) => {
- const eventType = req.headers["x-github-event"] as string;
- if (!eventType) {
- throw new HTTPError("Missing X-GitHub-Event header", 400);
- }
+ const eventType = req.headers["x-github-event"] as string;
+ if (!eventType) {
+ throw new HTTPError("Missing X-GitHub-Event header", 400);
+ }
- const discordPayload: WebhookExecuteSchema = {
- username: "GitHub",
- avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png",
- };
+ const discordPayload: WebhookExecuteSchema = {
+ username: "GitHub",
+ avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png",
+ };
- switch (eventType) {
- case "commit_comment":
- if (req.body.action !== "created") {
- return;
- }
+ switch (eventType) {
+ case "commit_comment":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``,
- description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- url: req.body.comment.html_url,
- },
- ];
- break;
- case "create":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`,
- },
- ];
- break;
- case "delete":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`,
- },
- ];
- break;
- case "fork":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`,
- url: req.body.forkee.html_url,
- },
- ];
- break;
- case "issue_comment":
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``,
+ description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ url: req.body.comment.html_url,
+ },
+ ];
+ break;
+ case "create":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`,
+ },
+ ];
+ break;
+ case "delete":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`,
+ },
+ ];
+ break;
+ case "fork":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`,
+ url: req.body.forkee.html_url,
+ },
+ ];
+ break;
+ case "issue_comment":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: "pull_request" in req.body.issue ? 12576191 : 15109472,
- title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${
- req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title
- }`,
- url: req.body.comment.html_url,
- description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- },
- ];
- break;
- case "issues":
- if (!["opened", "closed"].includes(req.body.action)) {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: "pull_request" in req.body.issue ? 12576191 : 15109472,
+ title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${
+ req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title
+ }`,
+ url: req.body.comment.html_url,
+ description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ },
+ ];
+ break;
+ case "issues":
+ if (!["opened", "closed"].includes(req.body.action)) {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`,
- url: req.body.issue.html_url,
- },
- ];
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`,
+ url: req.body.issue.html_url,
+ },
+ ];
- if (req.body.action === "opened") {
- discordPayload.embeds[0].color = 15426592;
- discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body;
- }
- break;
- case "member":
- if (req.body.action !== "added") {
- return;
- }
+ if (req.body.action === "opened") {
+ discordPayload.embeds[0].color = 15426592;
+ discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body;
+ }
+ break;
+ case "member":
+ if (req.body.action !== "added") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`,
- url: req.body.member.html_url,
- },
- ];
- break;
- case "public":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Now open sourced!`,
- },
- ];
- break;
- case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
- if (!["opened", "closed"].includes(req.body.action)) {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`,
+ url: req.body.member.html_url,
+ },
+ ];
+ break;
+ case "public":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Now open sourced!`,
+ },
+ ];
+ break;
+ case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
+ if (!["opened", "closed"].includes(req.body.action)) {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
- url: req.body.pull_request.html_url,
- },
- ];
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
+ url: req.body.pull_request.html_url,
+ },
+ ];
- if (req.body.action === "opened") {
- if (req.body.pull_request.body != null) {
- discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body;
- }
- discordPayload.embeds[0].color = 38912;
- }
- break;
- case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
- if (req.body.action !== "submitted") {
- return;
- }
+ if (req.body.action === "opened") {
+ if (req.body.pull_request.body != null) {
+ discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body;
+ }
+ discordPayload.embeds[0].color = 38912;
+ }
+ break;
+ case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
+ if (req.body.action !== "submitted") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
- description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body,
- url: req.body.review.html_url,
- },
- ];
- break;
- case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
+ description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body,
+ url: req.body.review.html_url,
+ },
+ ];
+ break;
+ case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: 12576191,
- title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
- description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- url: req.body.comment.html_url,
- },
- ];
- break;
- case "push":
- if (!req.body.ref.startsWith("refs/heads/")) {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: 12576191,
+ title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
+ description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ url: req.body.comment.html_url,
+ },
+ ];
+ break;
+ case "push":
+ if (!req.body.ref.startsWith("refs/heads/")) {
+ return;
+ }
- if (req.body.forced) {
- discordPayload.embeds = [
- {
- color: 16525609,
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``,
- description: `[Compare changes](${req.body.compare})`,
- },
- ];
- } else {
- discordPayload.embeds = [
- {
- color: 7506394,
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`,
- url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url,
- description: req.body.commits
- .slice(0, 5) // Discord only shows 5 first commits
- .map(
- (c: { id: string; url: string; message: string; author: { username: string } }) =>
- `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`,
- )
- .join("\n"),
- },
- ];
- }
- break;
- case "release":
- if (req.body.action !== "created") {
- return;
- }
+ if (req.body.forced) {
+ discordPayload.embeds = [
+ {
+ color: 16525609,
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``,
+ description: `[Compare changes](${req.body.compare})`,
+ },
+ ];
+ } else {
+ discordPayload.embeds = [
+ {
+ color: 7506394,
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`,
+ url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url,
+ description: req.body.commits
+ .slice(0, 5) // Discord only shows 5 first commits
+ .map(
+ (c: { id: string; url: string; message: string; author: { username: string } }) =>
+ `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`,
+ )
+ .join("\n"),
+ },
+ ];
+ }
+ break;
+ case "release":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`,
- url: req.body.release.html_url,
- },
- ];
- break;
- case "watch":
- if (req.body.action !== "started") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`,
+ url: req.body.release.html_url,
+ },
+ ];
+ break;
+ case "watch":
+ if (req.body.action !== "started") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New star added`,
- url: req.body.repository.html_url,
- },
- ];
- break;
- case "check_run":
- if (req.body.action !== "completed") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New star added`,
+ url: req.body.repository.html_url,
+ },
+ ];
+ break;
+ case "check_run":
+ if (req.body.action !== "completed") {
+ return;
+ }
- discordPayload.embeds = [
- {
- color: req.body.check_run.conclusion == "success" ? 38912 : 16525609,
- title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`,
- url: req.body.check_run.html_url,
- },
- ];
- break;
- case "check_suite":
- if (req.body.action !== "completed") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ color: req.body.check_run.conclusion == "success" ? 38912 : 16525609,
+ title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`,
+ url: req.body.check_run.html_url,
+ },
+ ];
+ break;
+ case "check_suite":
+ if (req.body.action !== "completed") {
+ return;
+ }
- discordPayload.embeds = [
- {
- color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609,
- title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`,
- url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`,
- },
- ];
- break;
- case "discussion":
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609,
+ title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`,
+ url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`,
+ },
+ ];
+ break;
+ case "discussion":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: 15109472,
- title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
- url: req.body.discussion.html_url,
- description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body,
- },
- ];
- break;
- case "discussion_comment":
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: 15109472,
+ title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
+ url: req.body.discussion.html_url,
+ description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body,
+ },
+ ];
+ break;
+ case "discussion_comment":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: 15109472,
- title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
- url: req.body.comment.html_url,
- description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- },
- ];
- break;
- default:
- return res.status(204).end(); // Yes, discord sends 204 even on invalid event
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: 15109472,
+ title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
+ url: req.body.comment.html_url,
+ description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ },
+ ];
+ break;
+ default:
+ return res.status(204).end(); // Yes, discord sends 204 even on invalid event
+ }
- req.body = discordPayload;
- req.query.wait ||= "true";
+ req.body = discordPayload;
+ req.query.wait ||= "true";
- next();
+ next();
};
router.post(
- "/",
- parseGitHubWebhook,
- (req, _res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
- next();
- },
- route({
- requestBody: "WebhookExecuteSchema",
- query: {
- wait: {
- type: "boolean",
- required: false,
- description: "waits for server confirmation of message send before response, and returns the created message body",
- },
- thread_id: {
- type: "string",
- required: false,
- description: "Send a message to the specified thread within a webhook's channel.",
- },
- },
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- executeWebhook,
+ "/",
+ parseGitHubWebhook,
+ (req, _res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
+ next();
+ },
+ route({
+ requestBody: "WebhookExecuteSchema",
+ query: {
+ wait: {
+ type: "boolean",
+ required: false,
+ description: "waits for server confirmation of message send before response, and returns the created message body",
+ },
+ thread_id: {
+ type: "string",
+ required: false,
+ description: "Send a message to the specified thread within a webhook's channel.",
+ },
+ },
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ executeWebhook,
);
export default router;
diff --git a/src/api/routes/webhooks/#webhook_id/#token/index.ts b/src/api/routes/webhooks/#webhook_id/#token/index.ts
index 1b589a7bb..eecbfcaea 100644
--- a/src/api/routes/webhooks/#webhook_id/#token/index.ts
+++ b/src/api/routes/webhooks/#webhook_id/#token/index.ts
@@ -8,179 +8,179 @@ import { WebhookUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a webhook object for the given id and token.",
- responses: {
- 200: {
- body: "APIWebhook",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id, token } = req.params;
- const webhook = await Webhook.findOne({
- where: {
- id: webhook_id,
- },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a webhook object for the given id and token.",
+ responses: {
+ 200: {
+ body: "APIWebhook",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id, token } = req.params;
+ const webhook = await Webhook.findOne({
+ where: {
+ id: webhook_id,
+ },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (!webhook) {
- throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- }
+ if (!webhook) {
+ throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ }
- if (webhook.token !== token) {
- throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
- }
+ if (webhook.token !== token) {
+ throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
+ }
- return res.json({
- ...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
- });
- },
+ return res.json({
+ ...webhook,
+ url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ });
+ },
);
// TODO: config max upload size
const messageUpload = multer({
- limits: {
- fileSize: Config.get().limits.message.maxAttachmentSize,
- fields: 10,
- // files: 1
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: Config.get().limits.message.maxAttachmentSize,
+ fields: 10,
+ // files: 1
+ },
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
// https://discord.com/developers/docs/resources/webhook#execute-webhook
// TODO: Slack compatible hooks
router.post(
- "/",
- messageUpload.any(),
- (req, _res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
+ "/",
+ messageUpload.any(),
+ (req, _res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- next();
- },
- route({
- requestBody: "WebhookExecuteSchema",
- query: {
- wait: {
- type: "boolean",
- required: false,
- description: "waits for server confirmation of message send before response, and returns the created message body",
- },
- thread_id: {
- type: "string",
- required: false,
- description: "Send a message to the specified thread within a webhook's channel.",
- },
- },
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- executeWebhook,
+ next();
+ },
+ route({
+ requestBody: "WebhookExecuteSchema",
+ query: {
+ wait: {
+ type: "boolean",
+ required: false,
+ description: "waits for server confirmation of message send before response, and returns the created message body",
+ },
+ thread_id: {
+ type: "string",
+ required: false,
+ description: "Send a message to the specified thread within a webhook's channel.",
+ },
+ },
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ executeWebhook,
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id, token } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id, token } = req.params;
- const webhook = await Webhook.findOne({
- where: {
- id: webhook_id,
- },
- relations: ["channel", "guild", "application"],
- });
+ const webhook = await Webhook.findOne({
+ where: {
+ id: webhook_id,
+ },
+ relations: ["channel", "guild", "application"],
+ });
- if (!webhook) {
- throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- }
+ if (!webhook) {
+ throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ }
- if (webhook.token !== token) {
- throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
- }
- const channel_id = webhook.channel_id;
- await Webhook.delete({ id: webhook_id });
+ if (webhook.token !== token) {
+ throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
+ }
+ const channel_id = webhook.channel_id;
+ await Webhook.delete({ id: webhook_id });
- await emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent);
+ await emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "WebhookUpdateSchema",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id, token } = req.params;
- const body = req.body as WebhookUpdateSchema;
+ "/",
+ route({
+ requestBody: "WebhookUpdateSchema",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id, token } = req.params;
+ const body = req.body as WebhookUpdateSchema;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
- const channel_id = webhook.channel_id;
- if (!body.name && !body.avatar) {
- throw new HTTPError("Empty webhook updates are not allowed", 50006);
- }
- if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
+ const channel_id = webhook.channel_id;
+ if (!body.name && !body.avatar) {
+ throw new HTTPError("Empty webhook updates are not allowed", 50006);
+ }
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
- if (body.name) {
- ValidateName(body.name);
- }
+ if (body.name) {
+ ValidateName(body.name);
+ }
- webhook.assign(body);
+ webhook.assign(body);
- await Promise.all([
- webhook.save(),
- emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent),
- ]);
- res.status(204);
- },
+ await Promise.all([
+ webhook.save(),
+ emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent),
+ ]);
+ res.status(204);
+ },
);
export default router;
diff --git a/src/api/routes/webhooks/#webhook_id/index.ts b/src/api/routes/webhooks/#webhook_id/index.ts
index 9c0a6418d..8844b8efb 100644
--- a/src/api/routes/webhooks/#webhook_id/index.ts
+++ b/src/api/routes/webhooks/#webhook_id/index.ts
@@ -6,141 +6,141 @@ import { WebhookUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.",
- responses: {
- 200: {
- body: "APIWebhook",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id } = req.params;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.",
+ responses: {
+ 200: {
+ body: "APIWebhook",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id } = req.params;
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (webhook.guild_id) {
- const permission = await getPermission(req.user_id, webhook.guild_id);
+ if (webhook.guild_id) {
+ const permission = await getPermission(req.user_id, webhook.guild_id);
- if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- return res.json({
- ...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
- });
- },
+ return res.json({
+ ...webhook,
+ url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ });
+ },
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id } = req.params;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (webhook.guild_id) {
- const permission = await getPermission(req.user_id, webhook.guild_id);
+ if (webhook.guild_id) {
+ const permission = await getPermission(req.user_id, webhook.guild_id);
- if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- const channel_id = webhook.channel_id;
- await Webhook.delete({ id: webhook_id });
+ const channel_id = webhook.channel_id;
+ await Webhook.delete({ id: webhook_id });
- await emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent);
+ await emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "WebhookUpdateSchema",
- responses: {
- 200: {
- body: "WebhookCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id } = req.params;
- const body = req.body as WebhookUpdateSchema;
+ "/",
+ route({
+ requestBody: "WebhookUpdateSchema",
+ responses: {
+ 200: {
+ body: "WebhookCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id } = req.params;
+ const body = req.body as WebhookUpdateSchema;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (webhook.guild_id) {
- const permission = await getPermission(req.user_id, webhook.guild_id);
+ if (webhook.guild_id) {
+ const permission = await getPermission(req.user_id, webhook.guild_id);
- if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- if (!body.name && !body.avatar && !body.channel_id) {
- throw new HTTPError("Empty webhook updates are not allowed", 50006);
- }
+ if (!body.name && !body.avatar && !body.channel_id) {
+ throw new HTTPError("Empty webhook updates are not allowed", 50006);
+ }
- if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
- if (body.name) {
- ValidateName(body.name);
- }
+ if (body.name) {
+ ValidateName(body.name);
+ }
- const channel_id = body.channel_id || webhook.channel_id;
- webhook.assign(body);
+ const channel_id = body.channel_id || webhook.channel_id;
+ webhook.assign(body);
- if (body.channel_id)
- webhook.assign({
- channel: await Channel.findOneOrFail({
- where: { id: channel_id },
- }),
- });
+ if (body.channel_id)
+ webhook.assign({
+ channel: await Channel.findOneOrFail({
+ where: { id: channel_id },
+ }),
+ });
- await Promise.all([
- webhook.save(),
- emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent),
- ]);
+ await Promise.all([
+ webhook.save(),
+ emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent),
+ ]);
- res.json(webhook);
- },
+ res.json(webhook);
+ },
);
export default router;
diff --git a/src/api/start.ts b/src/api/start.ts
index cd3373bec..96c0c348a 100644
--- a/src/api/start.ts
+++ b/src/api/start.ts
@@ -28,30 +28,30 @@ import cluster from "cluster";
import os from "os";
let cores = 1;
try {
- cores = Number(process.env.THREADS) || os.cpus().length;
+ cores = Number(process.env.THREADS) || os.cpus().length;
} catch {
- console.log("[API] Failed to get thread count! Using 1...");
+ console.log("[API] Failed to get thread count! Using 1...");
}
if (cluster.isPrimary && process.env.NODE_ENV == "production") {
- console.log(`Primary PID: ${process.pid}`);
+ console.log(`Primary PID: ${process.pid}`);
- // Fork workers.
- for (let i = 0; i < cores; i++) {
- cluster.fork();
- }
+ // Fork workers.
+ for (let i = 0; i < cores; i++) {
+ cluster.fork();
+ }
- cluster.on("exit", (worker) => {
- console.log(`Worker ${worker.process.pid} died, restarting worker`);
- cluster.fork();
- });
+ cluster.on("exit", (worker) => {
+ console.log(`Worker ${worker.process.pid} died, restarting worker`);
+ cluster.fork();
+ });
} else {
- const port = Number(process.env.PORT) || 3001;
+ const port = Number(process.env.PORT) || 3001;
- const server = new SpacebarServer({ port });
- server.start().catch(console.error);
+ const server = new SpacebarServer({ port });
+ server.start().catch(console.error);
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- global.server = server;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ global.server = server;
}
diff --git a/src/api/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts
index 86dda74b2..e71c3eda8 100644
--- a/src/api/util/handlers/Instance.ts
+++ b/src/api/util/handlers/Instance.ts
@@ -21,34 +21,34 @@ import { Like } from "typeorm";
import { setInterval } from "timers";
export async function initInstance() {
- // TODO: clean up database and delete tombstone data
- // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal
+ // TODO: clean up database and delete tombstone data
+ // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal
- // create default guild and add it to auto join
- // TODO: check if any current user is not part of autoJoinGuilds
- // const { autoJoin } = Config.get().guild;
+ // create default guild and add it to auto join
+ // TODO: check if any current user is not part of autoJoinGuilds
+ // const { autoJoin } = Config.get().guild;
- // if (autoJoin.enabled && !autoJoin.guilds?.length) {
- // const guild = await Guild.findOne({ where: {}, select: ["id"] });
- // if (guild) {
- // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
- // }
- // }
+ // if (autoJoin.enabled && !autoJoin.guilds?.length) {
+ // const guild = await Guild.findOne({ where: {}, select: ["id"] });
+ // if (guild) {
+ // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+ // }
+ // }
- // TODO: do no clear sessions for instance cluster
- // await Session.clear(); // This is now used as part of authentication...
- // ... but we can still expire temporary sessions for legacy tokens
- setInterval(
- async () => {
- for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) {
- // session object has all fields prefixed with `session_`... thanks typeorm
- if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) {
- console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`);
- await Session.delete({ session_id: session.session_session_id });
- }
- }
- },
- 1000 * 60 * 5,
- );
- // await Session.delete({ session_id: Like("TEMP_%") });
+ // TODO: do no clear sessions for instance cluster
+ // await Session.clear(); // This is now used as part of authentication...
+ // ... but we can still expire temporary sessions for legacy tokens
+ setInterval(
+ async () => {
+ for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) {
+ // session object has all fields prefixed with `session_`... thanks typeorm
+ if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) {
+ console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`);
+ await Session.delete({ session_id: session.session_session_id });
+ }
+ }
+ },
+ 1000 * 60 * 5,
+ );
+ // await Session.delete({ session_id: Like("TEMP_%") });
}
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 06a3e0817..65583f53a 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -18,36 +18,36 @@
import { EmbedHandlers } from "@spacebar/api";
import {
- Application,
- Attachment,
- Channel,
- Config,
- EmbedCache,
- emitEvent,
- EVERYONE_MENTION,
- getPermission,
- getRights,
- Guild,
- HERE_MENTION,
- Message,
- MessageCreateEvent,
- MessageUpdateEvent,
- Role,
- ROLE_MENTION,
- Sticker,
- User,
- //CHANNEL_MENTION,
- USER_MENTION,
- Webhook,
- handleFile,
- Permissions,
- normalizeUrl,
- DiscordApiErrors,
- CloudAttachment,
- ReadState,
- Member,
- Session,
- MessageFlags,
+ Application,
+ Attachment,
+ Channel,
+ Config,
+ EmbedCache,
+ emitEvent,
+ EVERYONE_MENTION,
+ getPermission,
+ getRights,
+ Guild,
+ HERE_MENTION,
+ Message,
+ MessageCreateEvent,
+ MessageUpdateEvent,
+ Role,
+ ROLE_MENTION,
+ Sticker,
+ User,
+ //CHANNEL_MENTION,
+ USER_MENTION,
+ Webhook,
+ handleFile,
+ Permissions,
+ normalizeUrl,
+ DiscordApiErrors,
+ CloudAttachment,
+ ReadState,
+ Member,
+ Session,
+ MessageFlags,
} from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { In, Or, Equal, IsNull } from "typeorm";
@@ -59,515 +59,515 @@ const allow_empty = false;
const LINK_REGEX = /?/g;
export async function handleMessage(opts: MessageOptions): Promise {
- const channel = await Channel.findOneOrFail({
- where: { id: opts.channel_id },
- relations: ["recipients"],
- });
- if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
+ const channel = await Channel.findOneOrFail({
+ where: { id: opts.channel_id },
+ relations: ["recipients"],
+ });
+ if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
- let permission: undefined | Permissions;
- const limit = channel.rate_limit_per_user;
+ let permission: undefined | Permissions;
+ const limit = channel.rate_limit_per_user;
- if (limit) {
- const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } }))
- ?.timestamp;
- if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) {
- permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
- //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks
- if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) {
- throw DiscordApiErrors.SLOWMODE_RATE_LIMIT;
- }
- }
- }
+ if (limit) {
+ const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } }))
+ ?.timestamp;
+ if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) {
+ permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
+ //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks
+ if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) {
+ throw DiscordApiErrors.SLOWMODE_RATE_LIMIT;
+ }
+ }
+ }
- const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined;
- // cloud attachments with indexes
- const cloudAttachments = opts.attachments?.reduce(
- (acc, att, index) => {
- if ("uploaded_filename" in att) {
- acc.push({ attachment: att, index });
- }
- return acc;
- },
- [] as { attachment: MessageCreateCloudAttachment; index: number }[],
- );
+ const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined;
+ // cloud attachments with indexes
+ const cloudAttachments = opts.attachments?.reduce(
+ (acc, att, index) => {
+ if ("uploaded_filename" in att) {
+ acc.push({ attachment: att, index });
+ }
+ return acc;
+ },
+ [] as { attachment: MessageCreateCloudAttachment; index: number }[],
+ );
- const message = Message.create({
- ...opts,
- poll: opts.poll,
- sticker_items: stickers,
- guild_id: channel.guild_id,
- channel_id: opts.channel_id,
- attachments: opts.attachments || [],
- embeds: opts.embeds || [],
- reactions: opts.reactions || [],
- type: opts.type ?? 0,
- mentions: [],
- components: opts.components ?? undefined, // Fix Discord-Go?
- });
- const ephermal = (message.flags & (1 << 6)) !== 0;
+ const message = Message.create({
+ ...opts,
+ poll: opts.poll,
+ sticker_items: stickers,
+ guild_id: channel.guild_id,
+ channel_id: opts.channel_id,
+ attachments: opts.attachments || [],
+ embeds: opts.embeds || [],
+ reactions: opts.reactions || [],
+ type: opts.type ?? 0,
+ mentions: [],
+ components: opts.components ?? undefined, // Fix Discord-Go?
+ });
+ const ephermal = (message.flags & (1 << 6)) !== 0;
- if (cloudAttachments && cloudAttachments.length > 0) {
- console.log("[Message] Processing attachments for message", message.id, ":", message.attachments);
- const uploadedAttachments = await Promise.all(
- cloudAttachments.map(async (att) => {
- const cAtt = att.attachment;
- const attEnt = await CloudAttachment.findOneOrFail({
- where: {
- uploadFilename: cAtt.uploaded_filename,
- },
- });
+ if (cloudAttachments && cloudAttachments.length > 0) {
+ console.log("[Message] Processing attachments for message", message.id, ":", message.attachments);
+ const uploadedAttachments = await Promise.all(
+ cloudAttachments.map(async (att) => {
+ const cAtt = att.attachment;
+ const attEnt = await CloudAttachment.findOneOrFail({
+ where: {
+ uploadFilename: cAtt.uploaded_filename,
+ },
+ });
- const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, {
- method: "POST",
- headers: {
- signature: Config.get().security.requestSignature || "",
- },
- });
+ const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, {
+ method: "POST",
+ headers: {
+ signature: Config.get().security.requestSignature || "",
+ },
+ });
- if (!cloneResponse.ok) {
- console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`);
- throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500);
- }
+ if (!cloneResponse.ok) {
+ console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`);
+ throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500);
+ }
- const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string };
+ const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string };
- const realAtt = Attachment.create({
- filename: attEnt.userFilename,
- url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
- proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
- size: attEnt.size,
- height: attEnt.height,
- width: attEnt.width,
- content_type: attEnt.contentType || attEnt.userOriginalContentType,
- });
- await realAtt.save();
- return { attachment: realAtt, index: att.index };
- }),
- );
- console.log("[Message] Processed attachments for message", message.id, ":", message.attachments);
+ const realAtt = Attachment.create({
+ filename: attEnt.userFilename,
+ url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
+ proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
+ size: attEnt.size,
+ height: attEnt.height,
+ width: attEnt.width,
+ content_type: attEnt.contentType || attEnt.userOriginalContentType,
+ });
+ await realAtt.save();
+ return { attachment: realAtt, index: att.index };
+ }),
+ );
+ console.log("[Message] Processed attachments for message", message.id, ":", message.attachments);
- for (const att of uploadedAttachments) {
- message.attachments![att.index] = att.attachment;
- }
- }
- // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments);
+ for (const att of uploadedAttachments) {
+ message.attachments![att.index] = att.attachment;
+ }
+ }
+ // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments);
- if (message.content && message.content.length > Config.get().limits.message.maxCharacters) {
- throw new HTTPError("Content length over max character limit");
- }
+ if (message.content && message.content.length > Config.get().limits.message.maxCharacters) {
+ throw new HTTPError("Content length over max character limit");
+ }
- if (opts.author_id) {
- message.author = await User.getPublicUser(opts.author_id);
- const rights = await getRights(opts.author_id);
- rights.hasThrow("SEND_MESSAGES");
- }
- if (opts.application_id) {
- message.application = await Application.findOneOrFail({
- where: { id: opts.application_id },
- });
- }
+ if (opts.author_id) {
+ message.author = await User.getPublicUser(opts.author_id);
+ const rights = await getRights(opts.author_id);
+ rights.hasThrow("SEND_MESSAGES");
+ }
+ if (opts.application_id) {
+ message.application = await Application.findOneOrFail({
+ where: { id: opts.application_id },
+ });
+ }
- if (opts.webhook_id) {
- message.webhook = await Webhook.findOneOrFail({
- where: { id: opts.webhook_id },
- });
+ if (opts.webhook_id) {
+ message.webhook = await Webhook.findOneOrFail({
+ where: { id: opts.webhook_id },
+ });
- message.author =
- (await User.findOne({
- where: { id: opts.webhook_id },
- })) || undefined;
+ message.author =
+ (await User.findOne({
+ where: { id: opts.webhook_id },
+ })) || undefined;
- if (!message.author) {
- message.author = User.create({
- id: opts.webhook_id,
- username: message.webhook.name,
- discriminator: "0000",
- avatar: message.webhook.avatar,
- public_flags: 0,
- premium: false,
- premium_type: 0,
- bot: true,
- created_at: new Date(),
- verified: true,
- rights: "0",
- data: {
- valid_tokens_since: new Date(),
- },
- });
+ if (!message.author) {
+ message.author = User.create({
+ id: opts.webhook_id,
+ username: message.webhook.name,
+ discriminator: "0000",
+ avatar: message.webhook.avatar,
+ public_flags: 0,
+ premium: false,
+ premium_type: 0,
+ bot: true,
+ created_at: new Date(),
+ verified: true,
+ rights: "0",
+ data: {
+ valid_tokens_since: new Date(),
+ },
+ });
- await message.author.save();
- }
+ await message.author.save();
+ }
- if (opts.username) {
- message.username = opts.username;
- message.author.username = message.username;
- }
- if (opts.avatar_url) {
- const avatarData = await fetch(opts.avatar_url);
- const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64"));
+ if (opts.username) {
+ message.username = opts.username;
+ message.author.username = message.username;
+ }
+ if (opts.avatar_url) {
+ const avatarData = await fetch(opts.avatar_url);
+ const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64"));
- const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64;
+ const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64;
- message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string);
- message.author.avatar = message.avatar;
- }
- } else {
- permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
- permission.hasThrow("SEND_MESSAGES");
- if (permission.cache.member) {
- message.member = permission.cache.member;
- }
+ message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string);
+ message.author.avatar = message.avatar;
+ }
+ } else {
+ permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
+ permission.hasThrow("SEND_MESSAGES");
+ if (permission.cache.member) {
+ message.member = permission.cache.member;
+ }
- if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES");
- if (opts.message_reference) {
- permission.hasThrow("READ_MESSAGE_HISTORY");
- // code below has to be redone when we add custom message routing
- if (message.guild_id !== null) {
- const guild = await Guild.findOneOrFail({
- where: { id: channel.guild_id },
- });
- if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id;
- if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id;
+ if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES");
+ if (opts.message_reference) {
+ permission.hasThrow("READ_MESSAGE_HISTORY");
+ // code below has to be redone when we add custom message routing
+ if (message.guild_id !== null) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: channel.guild_id },
+ });
+ if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id;
+ if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id;
- if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
- if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
- if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
- }
+ if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
+ if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
+ if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
+ }
- message.message_reference = opts.message_reference;
- message.referenced_message = await Message.findOneOrFail({
- where: {
- id: opts.message_reference.message_id,
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- });
+ message.message_reference = opts.message_reference;
+ message.referenced_message = await Message.findOneOrFail({
+ where: {
+ id: opts.message_reference.message_id,
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ });
- if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id)
- throw new HTTPError("Referenced message not found in the specified channel", 404);
- if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id)
- throw new HTTPError("Referenced message not found in the specified channel", 404);
- }
- /** Q: should be checked if the referenced message exists? ANSWER: NO
+ if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id)
+ throw new HTTPError("Referenced message not found in the specified channel", 404);
+ if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id)
+ throw new HTTPError("Referenced message not found in the specified channel", 404);
+ }
+ /** Q: should be checked if the referenced message exists? ANSWER: NO
otherwise backfilling won't work **/
- message.type = MessageType.REPLY;
- }
- }
+ message.type = MessageType.REPLY;
+ }
+ }
- // TODO: stickers/activity
- if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) {
- console.log("[Message] Rejecting empty message:", opts, message);
- throw new HTTPError("Empty messages are not allowed", 50006);
- }
+ // TODO: stickers/activity
+ if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) {
+ console.log("[Message] Rejecting empty message:", opts, message);
+ throw new HTTPError("Empty messages are not allowed", 50006);
+ }
- let content = opts.content;
+ let content = opts.content;
- // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
- //const mention_channel_ids = [] as string[];
- const mention_role_ids = [] as string[];
- const mention_user_ids = [] as string[];
- let mention_everyone = false;
+ // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
+ //const mention_channel_ids = [] as string[];
+ const mention_role_ids = [] as string[];
+ const mention_user_ids = [] as string[];
+ let mention_everyone = false;
- if (content) {
- // TODO: explicit-only mentions
- message.content = content.trim();
- content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks
- // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
- /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) {
+ if (content) {
+ // TODO: explicit-only mentions
+ message.content = content.trim();
+ content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks
+ // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
+ /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) {
if (!mention_channel_ids.includes(mention))
mention_channel_ids.push(mention);
}*/
- for (const [, mention] of content.matchAll(USER_MENTION)) {
- if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention);
- }
+ for (const [, mention] of content.matchAll(USER_MENTION)) {
+ if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention);
+ }
- await Promise.all(
- Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => {
- const role = await Role.findOneOrFail({
- where: { id: mention, guild_id: channel.guild_id },
- });
- if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) {
- mention_role_ids.push(mention);
- }
- }),
- );
+ await Promise.all(
+ Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => {
+ const role = await Role.findOneOrFail({
+ where: { id: mention, guild_id: channel.guild_id },
+ });
+ if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) {
+ mention_role_ids.push(mention);
+ }
+ }),
+ );
- if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) {
- mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION);
- }
- }
+ if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) {
+ mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION);
+ }
+ }
- if (message.message_reference?.message_id) {
- const referencedMessage = await Message.findOne({
- where: {
- id: message.message_reference.message_id,
- channel_id: message.channel_id,
- },
- });
- if (referencedMessage && referencedMessage.author_id !== message.author_id) {
- message.mentions.push(
- User.create({
- id: referencedMessage.author_id,
- }),
- );
- }
- }
+ if (message.message_reference?.message_id) {
+ const referencedMessage = await Message.findOne({
+ where: {
+ id: message.message_reference.message_id,
+ channel_id: message.channel_id,
+ },
+ });
+ if (referencedMessage && referencedMessage.author_id !== message.author_id) {
+ message.mentions.push(
+ User.create({
+ id: referencedMessage.author_id,
+ }),
+ );
+ }
+ }
- // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
- /*message.mention_channels = mention_channel_ids.map((x) =>
+ // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
+ /*message.mention_channels = mention_channel_ids.map((x) =>
Channel.create({ id: x }),
);*/
- message.mention_roles = (
- await Promise.all(
- mention_role_ids.map((x) => {
- return Role.findOne({ where: { id: x } });
- }),
- )
- ).filter((role) => role !== null);
+ message.mention_roles = (
+ await Promise.all(
+ mention_role_ids.map((x) => {
+ return Role.findOne({ where: { id: x } });
+ }),
+ )
+ ).filter((role) => role !== null);
- message.mentions = [
- ...message.mentions,
- ...(
- await Promise.all(
- mention_user_ids.map((x) => {
- return User.findOne({ where: { id: x } });
- }),
- )
- ).filter((user) => user !== null),
- ];
+ message.mentions = [
+ ...message.mentions,
+ ...(
+ await Promise.all(
+ mention_user_ids.map((x) => {
+ return User.findOne({ where: { id: x } });
+ }),
+ )
+ ).filter((user) => user !== null),
+ ];
- message.mention_everyone = mention_everyone;
- async function fillInMissingIDs(ids: string[]) {
- const states = await ReadState.findBy({
- user_id: Or(...ids.map((id) => Equal(id))),
- channel_id: channel.id,
- });
- const users = new Set(ids);
- states.forEach((state) => users.delete(state.user_id));
- if (!users.size) {
- return;
- }
- return Promise.all(
- [...users].map((user_id) => {
- return ReadState.create({ user_id, channel_id: channel.id }).save();
- }),
- );
- }
- if (ephermal) {
- const id = message.interaction_metadata?.user_id;
- if (id) {
- let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM;
- if (!pinged) pinged = !!message.mentions.find((user) => user.id === id);
- if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } }));
- if (pinged) {
- //stuff
- }
- }
- } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
- if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
- if (channel.recipients) {
- await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id));
- }
- } else {
- console.log(channel.guild_id);
- await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id));
- }
- const repository = ReadState.getRepository();
- const condition = { channel_id: channel.id };
- await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
- await repository.increment(condition, "mention_count", 1);
- } else {
- const users = new Set([
- ...(message.mention_roles.length
- ? await Member.find({
- where: [
- ...message.mention_roles.map((role) => {
- return { roles: { id: role.id } };
- }),
- ],
- })
- : []
- ).map((member) => member.id),
- ...message.mentions.map((user) => user.id),
- ]);
- if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) {
- const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id);
- (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id));
- }
- if (users.size) {
- const repository = ReadState.getRepository();
- const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id };
+ message.mention_everyone = mention_everyone;
+ async function fillInMissingIDs(ids: string[]) {
+ const states = await ReadState.findBy({
+ user_id: Or(...ids.map((id) => Equal(id))),
+ channel_id: channel.id,
+ });
+ const users = new Set(ids);
+ states.forEach((state) => users.delete(state.user_id));
+ if (!users.size) {
+ return;
+ }
+ return Promise.all(
+ [...users].map((user_id) => {
+ return ReadState.create({ user_id, channel_id: channel.id }).save();
+ }),
+ );
+ }
+ if (ephermal) {
+ const id = message.interaction_metadata?.user_id;
+ if (id) {
+ let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM;
+ if (!pinged) pinged = !!message.mentions.find((user) => user.id === id);
+ if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } }));
+ if (pinged) {
+ //stuff
+ }
+ }
+ } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
+ if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
+ if (channel.recipients) {
+ await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id));
+ }
+ } else {
+ console.log(channel.guild_id);
+ await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id));
+ }
+ const repository = ReadState.getRepository();
+ const condition = { channel_id: channel.id };
+ await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
+ await repository.increment(condition, "mention_count", 1);
+ } else {
+ const users = new Set([
+ ...(message.mention_roles.length
+ ? await Member.find({
+ where: [
+ ...message.mention_roles.map((role) => {
+ return { roles: { id: role.id } };
+ }),
+ ],
+ })
+ : []
+ ).map((member) => member.id),
+ ...message.mentions.map((user) => user.id),
+ ]);
+ if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) {
+ const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id);
+ (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id));
+ }
+ if (users.size) {
+ const repository = ReadState.getRepository();
+ const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id };
- await fillInMissingIDs([...users]);
+ await fillInMissingIDs([...users]);
- await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
- await repository.increment(condition, "mention_count", 1);
- }
- }
+ await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
+ await repository.increment(condition, "mention_count", 1);
+ }
+ }
- // TODO: check and put it all in the body
+ // TODO: check and put it all in the body
- return message;
+ return message;
}
// TODO: cache link result in db
export async function postHandleMessage(message: Message) {
- const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
+ const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
- const linkMatches = content?.match(LINK_REGEX) || [];
+ const linkMatches = content?.match(LINK_REGEX) || [];
- const data = { ...message };
+ const data = { ...message };
- const currentNormalizedUrls = new Set();
- for (const link of linkMatches) {
- // Don't process links in <>
- if (link.startsWith("<") && link.endsWith(">")) {
- continue;
- }
- try {
- const normalized = normalizeUrl(link);
- currentNormalizedUrls.add(normalized);
- } catch (e) {
- continue;
- }
- }
+ const currentNormalizedUrls = new Set();
+ for (const link of linkMatches) {
+ // Don't process links in <>
+ if (link.startsWith("<") && link.endsWith(">")) {
+ continue;
+ }
+ try {
+ const normalized = normalizeUrl(link);
+ currentNormalizedUrls.add(normalized);
+ } catch (e) {
+ continue;
+ }
+ }
- data.embeds.forEach((embed) => {
- if (!embed.type) {
- embed.type = EmbedType.rich;
- }
- });
- // Filter out embeds that could be links, start from scratch
- data.embeds = data.embeds.filter((embed) => embed.type === "rich");
+ data.embeds.forEach((embed) => {
+ if (!embed.type) {
+ embed.type = EmbedType.rich;
+ }
+ });
+ // Filter out embeds that could be links, start from scratch
+ data.embeds = data.embeds.filter((embed) => embed.type === "rich");
- const seenNormalizedUrls = new Set();
- const uniqueLinks: string[] = [];
+ const seenNormalizedUrls = new Set();
+ const uniqueLinks: string[] = [];
- for (const link of linkMatches.slice(0, 20)) {
- // embed max 20 links - TODO: make this configurable with instance policies
- // Don't embed links in <>
- if (link.startsWith("<") && link.endsWith(">")) continue;
+ for (const link of linkMatches.slice(0, 20)) {
+ // embed max 20 links - TODO: make this configurable with instance policies
+ // Don't embed links in <>
+ if (link.startsWith("<") && link.endsWith(">")) continue;
- try {
- const normalized = normalizeUrl(link);
+ try {
+ const normalized = normalizeUrl(link);
- if (!seenNormalizedUrls.has(normalized)) {
- seenNormalizedUrls.add(normalized);
- uniqueLinks.push(link);
- }
- } catch (e) {
- // Invalid URL, skip
- continue;
- }
- }
+ if (!seenNormalizedUrls.has(normalized)) {
+ seenNormalizedUrls.add(normalized);
+ uniqueLinks.push(link);
+ }
+ } catch (e) {
+ // Invalid URL, skip
+ continue;
+ }
+ }
- if (uniqueLinks.length === 0) {
- // No valid unique links found, update message to remove old embeds
- data.embeds = data.embeds.filter((embed) => embed.type === "rich");
- const author = data.author?.toPublicUser();
- const event = {
- event: "MESSAGE_UPDATE",
- channel_id: message.channel_id,
- data: {
- ...data,
- author,
- },
- } as MessageUpdateEvent;
- await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]);
- return;
- }
+ if (uniqueLinks.length === 0) {
+ // No valid unique links found, update message to remove old embeds
+ data.embeds = data.embeds.filter((embed) => embed.type === "rich");
+ const author = data.author?.toPublicUser();
+ const event = {
+ event: "MESSAGE_UPDATE",
+ channel_id: message.channel_id,
+ data: {
+ ...data,
+ author,
+ },
+ } as MessageUpdateEvent;
+ await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]);
+ return;
+ }
- const cachePromises = [];
+ const cachePromises = [];
- for (const link of uniqueLinks) {
- let url: URL;
- try {
- url = new URL(link);
- } catch (e) {
- // Skip invalid URLs
- continue;
- }
+ for (const link of uniqueLinks) {
+ let url: URL;
+ try {
+ url = new URL(link);
+ } catch (e) {
+ // Skip invalid URLs
+ continue;
+ }
- const normalizedUrl = normalizeUrl(link);
+ const normalizedUrl = normalizeUrl(link);
- // Check cache using normalized URL
- const cached = await EmbedCache.findOne({
- where: { url: normalizedUrl },
- });
+ // Check cache using normalized URL
+ const cached = await EmbedCache.findOne({
+ where: { url: normalizedUrl },
+ });
- if (cached) {
- data.embeds.push(cached.embed);
- continue;
- }
+ if (cached) {
+ data.embeds.push(cached.embed);
+ continue;
+ }
- // bit gross, but whatever!
- const endpointPublic = Config.get().cdn.endpointPublic; // lol
- const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
+ // bit gross, but whatever!
+ const endpointPublic = Config.get().cdn.endpointPublic; // lol
+ const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
- try {
- let res = await handler(url);
- if (!res) continue;
- // tried to use shorthand but types didn't like me L
- if (!Array.isArray(res)) res = [res];
+ try {
+ let res = await handler(url);
+ if (!res) continue;
+ // tried to use shorthand but types didn't like me L
+ if (!Array.isArray(res)) res = [res];
- for (const embed of res) {
- // Cache with normalized URL
- const cache = EmbedCache.create({
- url: normalizedUrl,
- embed: embed,
- });
- cachePromises.push(cache.save());
- data.embeds.push(embed);
- }
- } catch (e) {
- console.error(`[Embeds] Error while generating embed for ${link}`, e);
- }
- }
+ for (const embed of res) {
+ // Cache with normalized URL
+ const cache = EmbedCache.create({
+ url: normalizedUrl,
+ embed: embed,
+ });
+ cachePromises.push(cache.save());
+ data.embeds.push(embed);
+ }
+ } catch (e) {
+ console.error(`[Embeds] Error while generating embed for ${link}`, e);
+ }
+ }
- await Promise.all([
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id: message.channel_id,
- data,
- } as MessageUpdateEvent),
- Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }),
- ...cachePromises,
- ]);
+ await Promise.all([
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id: message.channel_id,
+ data,
+ } as MessageUpdateEvent),
+ Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }),
+ ...cachePromises,
+ ]);
}
export async function sendMessage(opts: MessageOptions) {
- const message = await handleMessage({ ...opts, timestamp: new Date() });
+ const message = await handleMessage({ ...opts, timestamp: new Date() });
- const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0;
- await Promise.all([
- Message.insert(message),
- emitEvent({
- event: "MESSAGE_CREATE",
- ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }),
- data: message.toJSON(),
- } as MessageCreateEvent),
- ]);
+ const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0;
+ await Promise.all([
+ Message.insert(message),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }),
+ data: message.toJSON(),
+ } as MessageCreateEvent),
+ ]);
- // no await as it should catch error non-blockingly
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ // no await as it should catch error non-blockingly
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- return message;
+ return message;
}
interface MessageOptions extends MessageCreateSchema {
- id?: string;
- type?: MessageType;
- pinned?: boolean;
- author_id?: string;
- webhook_id?: string;
- application_id?: string;
- embeds?: Embed[];
- reactions?: Reaction[];
- channel_id?: string;
- attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this?
- edited_timestamp?: Date;
- timestamp?: Date;
- username?: string;
- avatar_url?: string;
+ id?: string;
+ type?: MessageType;
+ pinned?: boolean;
+ author_id?: string;
+ webhook_id?: string;
+ application_id?: string;
+ embeds?: Embed[];
+ reactions?: Reaction[];
+ channel_id?: string;
+ attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this?
+ edited_timestamp?: Date;
+ timestamp?: Date;
+ username?: string;
+ avatar_url?: string;
}
diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index e34b67947..409338f75 100644
--- a/src/api/util/handlers/Voice.ts
+++ b/src/api/util/handlers/Voice.ts
@@ -20,31 +20,31 @@ import { Config, IpDataClient } from "@spacebar/util";
import { distanceBetweenLocations } from "../utility/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 IpDataClient.getIpInfo(ipAddress);
+ if (!regions.useDefaultAsOptimal) {
+ const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress);
- let min = Number.POSITIVE_INFINITY;
+ let min = Number.POSITIVE_INFINITY;
- for (const 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 IpDataClient.getIpInfo(ar.endpoint))!);
+ for (const 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 IpDataClient.getIpInfo(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,
- }));
+ return availableRegions.map((ar) => ({
+ id: ar.id,
+ name: ar.name,
+ custom: ar.custom,
+ deprecated: ar.deprecated,
+ optimal: ar.id === optimalId,
+ }));
}
diff --git a/src/api/util/handlers/Webhook.ts b/src/api/util/handlers/Webhook.ts
index 54ef26ecb..cd0f2f6ef 100644
--- a/src/api/util/handlers/Webhook.ts
+++ b/src/api/util/handlers/Webhook.ts
@@ -6,118 +6,118 @@ import { MoreThan } from "typeorm";
import { WebhookExecuteSchema } from "@spacebar/schemas";
export const executeWebhook = async (req: Request, res: Response) => {
- const body = req.body as WebhookExecuteSchema;
+ const body = req.body as WebhookExecuteSchema;
- const { webhook_id, token } = req.params;
+ const { webhook_id, token } = req.params;
- const webhook = await Webhook.findOne({
- where: {
- id: webhook_id,
- },
- relations: ["channel", "guild", "application"],
- });
+ const webhook = await Webhook.findOne({
+ where: {
+ id: webhook_id,
+ },
+ relations: ["channel", "guild", "application"],
+ });
- if (!webhook) {
- throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- }
+ if (!webhook) {
+ throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ }
- if (webhook.token !== token) {
- throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
- }
+ if (webhook.token !== token) {
+ throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
+ }
- if (body.username) {
- ValidateName(body.username);
- }
+ if (body.username) {
+ ValidateName(body.username);
+ }
- // ensure one of content, embeds, components, or file is present
- if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) {
- throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE;
- }
+ // ensure one of content, embeds, components, or file is present
+ if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) {
+ throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE;
+ }
- const wait = req.query.wait === "true";
+ const wait = req.query.wait === "true";
- if (!wait) {
- res.status(204).send();
- }
+ if (!wait) {
+ res.status(204).send();
+ }
- const attachments: Attachment[] = [];
+ const attachments: Attachment[] = [];
- if (!webhook.channel.isWritable()) {
- if (wait) {
- throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400);
- } else {
- return;
- }
- }
+ if (!webhook.channel.isWritable()) {
+ if (wait) {
+ throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400);
+ } else {
+ return;
+ }
+ }
- // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one?
- const limits = Config.get().limits;
- if (limits.absoluteRate.register.enabled) {
- const count = await Message.count({
- where: {
- channel_id: webhook.channel_id,
- timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
- },
- });
+ // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one?
+ const limits = Config.get().limits;
+ if (limits.absoluteRate.register.enabled) {
+ const count = await Message.count({
+ where: {
+ channel_id: webhook.channel_id,
+ timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
+ },
+ });
- if (count >= limits.absoluteRate.sendMessage.limit)
- if (wait) {
- throw FieldErrors({
- channel_id: {
- code: "TOO_MANY_MESSAGES",
- message: req.t("common:toomany.MESSAGE"),
- },
- });
- } else {
- return;
- }
- }
+ if (count >= limits.absoluteRate.sendMessage.limit)
+ if (wait) {
+ throw FieldErrors({
+ channel_id: {
+ code: "TOO_MANY_MESSAGES",
+ message: req.t("common:toomany.MESSAGE"),
+ },
+ });
+ } else {
+ return;
+ }
+ }
- const files = (req.files as Express.Multer.File[]) ?? [];
- for (const currFile of files) {
- try {
- const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- } catch (error) {
- if (wait) res.status(400).json({ message: error?.toString() });
- return;
- }
- }
+ const files = (req.files as Express.Multer.File[]) ?? [];
+ for (const currFile of files) {
+ try {
+ const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile);
+ attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ } catch (error) {
+ if (wait) res.status(400).json({ message: error?.toString() });
+ return;
+ }
+ }
- const embeds = body.embeds || [];
- const message = await handleMessage({
- ...body,
- username: body.username || webhook.name,
- avatar_url: body.avatar_url || webhook.avatar,
- type: 0,
- pinned: false,
- webhook_id: webhook.id,
- application_id: webhook.application?.id,
- embeds,
- // TODO: Support thread_id/thread_name once threads are implemented
- channel_id: webhook.channel_id,
- attachments,
- timestamp: new Date(),
- });
+ const embeds = body.embeds || [];
+ const message = await handleMessage({
+ ...body,
+ username: body.username || webhook.name,
+ avatar_url: body.avatar_url || webhook.avatar,
+ type: 0,
+ pinned: false,
+ webhook_id: webhook.id,
+ application_id: webhook.application?.id,
+ embeds,
+ // TODO: Support thread_id/thread_name once threads are implemented
+ channel_id: webhook.channel_id,
+ attachments,
+ timestamp: new Date(),
+ });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore dont care2
- message.edited_timestamp = null;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore dont care2
+ message.edited_timestamp = null;
- webhook.channel.last_message_id = message.id;
+ webhook.channel.last_message_id = message.id;
- await Promise.all([
- message.save(),
- webhook.channel.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: webhook.channel_id,
- data: message,
- } as MessageCreateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ webhook.channel.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: webhook.channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ ]);
- // no await as it shouldnt block the message send function and silently catch error
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- if (wait) res.json(message);
- return;
+ // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ if (wait) res.json(message);
+ return;
};
diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index bb38c5303..7ae00a555 100644
--- a/src/api/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -22,107 +22,107 @@ import { NextFunction, Request, Response } from "express";
import { ajv } from "@spacebar/schemas";
const ignoredRequestSchemas = [
- // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix?
- "SettingsProtoUpdateJsonSchema",
+ // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix?
+ "SettingsProtoUpdateJsonSchema",
];
declare global {
- // TODO: fix this
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace Express {
- interface Request {
- permission?: Permissions;
- }
- }
+ // TODO: fix this
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ permission?: Permissions;
+ }
+ }
}
export type RouteResponse = {
- status?: number;
- body?: `${string}Response`;
- headers?: Record;
+ status?: number;
+ body?: `${string}Response`;
+ headers?: Record;
};
export interface RouteOptions {
- permission?: PermissionResolvable;
- right?: RightResolvable;
- requestBody?: `${string}Schema`; // typescript interface name
- responses?: {
- [status: number]: {
- // body?: `${string}Response`;
- body?: string;
- };
- };
- event?: EVENT | EVENT[];
- summary?: string;
- description?: string;
- query?: {
- [key: string]: {
- type: string;
- required?: boolean;
- description?: string;
- values?: string[];
- };
- };
- deprecated?: boolean;
- // test?: {
- // response?: RouteResponse;
- // body?: unknown;
- // path?: string;
- // event?: EVENT | EVENT[];
- // headers?: Record;
- // };
+ permission?: PermissionResolvable;
+ right?: RightResolvable;
+ requestBody?: `${string}Schema`; // typescript interface name
+ responses?: {
+ [status: number]: {
+ // body?: `${string}Response`;
+ body?: string;
+ };
+ };
+ event?: EVENT | EVENT[];
+ summary?: string;
+ description?: string;
+ query?: {
+ [key: string]: {
+ type: string;
+ required?: boolean;
+ description?: string;
+ values?: string[];
+ };
+ };
+ deprecated?: boolean;
+ // test?: {
+ // response?: RouteResponse;
+ // body?: unknown;
+ // path?: string;
+ // event?: EVENT | EVENT[];
+ // headers?: Record;
+ // };
}
export function route(opts: RouteOptions) {
- let validate: AnyValidateFunction | undefined;
- if (opts.requestBody) {
- try {
- validate = ajv.getSchema(opts.requestBody);
- } catch (e) {
- console.error("AJV getSchema failed!");
- throw e;
- }
+ let validate: AnyValidateFunction | undefined;
+ if (opts.requestBody) {
+ try {
+ validate = ajv.getSchema(opts.requestBody);
+ } catch (e) {
+ console.error("AJV getSchema failed!");
+ throw e;
+ }
- if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`);
- }
+ if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`);
+ }
- return async (req: Request, res: Response, next: NextFunction) => {
- if (opts.permission) {
- req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
+ return async (req: Request, res: Response, next: NextFunction) => {
+ if (opts.permission) {
+ req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
- const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission];
- requiredPerms.forEach((perm) => {
- // bitfield comparison: check if user lacks certain permission
- if (!req.permission!.has(new Permissions(perm))) {
- throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string);
- }
- });
- }
+ const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission];
+ requiredPerms.forEach((perm) => {
+ // bitfield comparison: check if user lacks certain permission
+ if (!req.permission!.has(new Permissions(perm))) {
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string);
+ }
+ });
+ }
- if (opts.right) {
- const required = new Rights(opts.right);
- req.rights = await getRights(req.user_id);
+ if (opts.right) {
+ const required = new Rights(opts.right);
+ req.rights = await getRights(req.user_id);
- if (!req.rights || !req.rights.has(required)) {
- throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
- }
- }
+ if (!req.rights || !req.rights.has(required)) {
+ throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
+ }
+ }
- if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
- const valid = validate(req.body);
- if (!valid) {
- const fields: Record = {};
- validate.errors?.forEach(
- (x) =>
- (fields[x.instancePath.slice(1)] = {
- code: x.keyword,
- message: x.message || "",
- }),
- );
- if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
- throw FieldErrors(fields, validate.errors!);
- }
- }
- next();
- };
+ if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
+ const valid = validate(req.body);
+ if (!valid) {
+ const fields: Record = {};
+ validate.errors?.forEach(
+ (x) =>
+ (fields[x.instancePath.slice(1)] = {
+ code: x.keyword,
+ message: x.message || "",
+ }),
+ );
+ if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
+ throw FieldErrors(fields, validate.errors!);
+ }
+ }
+ next();
+ };
}
diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 7426d22bf..78a56f635 100644
--- a/src/api/util/utility/Base64.ts
+++ b/src/api/util/utility/Base64.ts
@@ -25,41 +25,41 @@ const b2s = alphabet.split("");
// 123 == 'z'.charCodeAt(0) + 1
const s2b = new Array(123);
for (let i = 0; i < alphabet.length; i++) {
- s2b[alphabet.charCodeAt(i)] = i;
+ s2b[alphabet.charCodeAt(i)] = i;
}
// number to base64
export const ntob = (n: number): string => {
- if (n < 0) return `-${ntob(-n)}`;
+ if (n < 0) return `-${ntob(-n)}`;
- let lo = n >>> 0;
- let hi = (n / 4294967296) >>> 0;
+ let lo = n >>> 0;
+ let hi = (n / 4294967296) >>> 0;
- let right = "";
- while (hi > 0) {
- right = b2s[0x3f & lo] + right;
- lo >>>= 6;
- lo |= (0x3f & hi) << 26;
- hi >>>= 6;
- }
+ let right = "";
+ while (hi > 0) {
+ right = b2s[0x3f & lo] + right;
+ lo >>>= 6;
+ lo |= (0x3f & hi) << 26;
+ hi >>>= 6;
+ }
- let left = "";
- do {
- left = b2s[0x3f & lo] + left;
- lo >>>= 6;
- } while (lo > 0);
+ let left = "";
+ do {
+ left = b2s[0x3f & lo] + left;
+ lo >>>= 6;
+ } while (lo > 0);
- return left + right;
+ return left + right;
};
// base64 to number
export const bton = (base64: string) => {
- let number = 0;
- const sign = base64.charAt(0) === "-" ? 1 : 0;
+ let number = 0;
+ const sign = base64.charAt(0) === "-" ? 1 : 0;
- for (let i = sign; i < base64.length; i++) {
- number = number * 64 + s2b[base64.charCodeAt(i)];
- }
+ for (let i = sign; i < base64.length; i++) {
+ number = number * 64 + s2b[base64.charCodeAt(i)];
+ }
- return sign ? -number : number;
+ return sign ? -number : number;
};
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index 8bbd62834..2e50c536e 100644
--- a/src/api/util/utility/EmbedHandlers.ts
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -24,494 +24,494 @@ import { yellow } from "picocolors";
import probe from "probe-image-size";
export const DEFAULT_FETCH_OPTIONS: RequestInit = {
- redirect: "follow",
- headers: {
- "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)",
- },
- // size: 1024 * 1024 * 5, // grabbed from config later
- method: "GET",
+ redirect: "follow",
+ headers: {
+ "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)",
+ },
+ // size: 1024 * 1024 * 5, // grabbed from config later
+ method: "GET",
};
const makeEmbedImage = (url: string | undefined, width: number | undefined, height: number | undefined): Required | undefined => {
- if (!url || !width || !height) return undefined;
- return {
- url,
- width,
- height,
- proxy_url: getProxyUrl(new URL(url), width, height),
- };
+ if (!url || !width || !height) return undefined;
+ return {
+ url,
+ width,
+ height,
+ proxy_url: getProxyUrl(new URL(url), width, height),
+ };
};
let hasWarnedAboutImagor = false;
export const getProxyUrl = (url: URL, width: number, height: number): string => {
- const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
- const secret = Config.get().security.requestSignature;
- width = Math.min(width || 500, resizeWidthMax || width);
- height = Math.min(height || 500, resizeHeightMax || width);
+ const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
+ const secret = Config.get().security.requestSignature;
+ width = Math.min(width || 500, resizeWidthMax || width);
+ height = Math.min(height || 500, resizeHeightMax || width);
- // Imagor
- if (imagorServerUrl) {
- const path = `${width}x${height}/${url.host}${url.pathname}`;
+ // Imagor
+ if (imagorServerUrl) {
+ const path = `${width}x${height}/${url.host}${url.pathname}`;
- const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
+ const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
- return `${imagorServerUrl}/${hash}/${path}`;
- }
+ return `${imagorServerUrl}/${hash}/${path}`;
+ }
- if (!hasWarnedAboutImagor) {
- hasWarnedAboutImagor = true;
- console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/"));
- }
+ if (!hasWarnedAboutImagor) {
+ hasWarnedAboutImagor = true;
+ console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/"));
+ }
- return url.toString();
+ return url.toString();
};
const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => {
- let elem = $(`meta[property="${name}"]`);
- if (!elem.length) elem = $(`meta[name="${name}"]`);
- const ret = elem.attr("content") || elem.text();
- return ret.trim().length == 0 ? undefined : ret;
+ let elem = $(`meta[property="${name}"]`);
+ if (!elem.length) elem = $(`meta[name="${name}"]`);
+ const ret = elem.attr("content") || elem.text();
+ return ret.trim().length == 0 ? undefined : ret;
};
const tryParseInt = (str: string | undefined) => {
- if (!str) return undefined;
- try {
- return parseInt(str);
- } catch (e) {
- return undefined;
- }
+ if (!str) return undefined;
+ try {
+ return parseInt(str);
+ } catch (e) {
+ return undefined;
+ }
};
export const getMetaDescriptions = (text: string) => {
- const $ = cheerio.load(text);
+ const $ = cheerio.load(text);
- return {
- type: getMeta($, "og:type"),
- title: getMeta($, "og:title") || $("title").first().text(),
- provider_name: getMeta($, "og:site_name"),
- author: getMeta($, "article:author"),
- description: getMeta($, "og:description") || getMeta($, "description"),
- image: getMeta($, "og:image") || getMeta($, "twitter:image"),
- image_fallback: $(`image`).attr("src"),
- video_fallback: $(`video`).attr("src"),
- width: tryParseInt(getMeta($, "og:image:width")),
- height: tryParseInt(getMeta($, "og:image:height")),
- url: getMeta($, "og:url"),
- youtube_embed: getMeta($, "og:video:secure_url"),
- site_name: getMeta($, "og:site_name"),
+ return {
+ type: getMeta($, "og:type"),
+ title: getMeta($, "og:title") || $("title").first().text(),
+ provider_name: getMeta($, "og:site_name"),
+ author: getMeta($, "article:author"),
+ description: getMeta($, "og:description") || getMeta($, "description"),
+ image: getMeta($, "og:image") || getMeta($, "twitter:image"),
+ image_fallback: $(`image`).attr("src"),
+ video_fallback: $(`video`).attr("src"),
+ width: tryParseInt(getMeta($, "og:image:width")),
+ height: tryParseInt(getMeta($, "og:image:height")),
+ url: getMeta($, "og:url"),
+ youtube_embed: getMeta($, "og:video:secure_url"),
+ site_name: getMeta($, "og:site_name"),
- $,
- };
+ $,
+ };
};
const doFetch = async (url: URL) => {
- try {
- const res = await fetch(url, {
- ...DEFAULT_FETCH_OPTIONS,
- });
- if (res.headers.get("content-length")) {
- const contentLength = parseInt(res.headers.get("content-length")!);
- if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) {
- return null;
- }
- }
- return res;
- } catch (e) {
- return null;
- }
+ try {
+ const res = await fetch(url, {
+ ...DEFAULT_FETCH_OPTIONS,
+ });
+ if (res.headers.get("content-length")) {
+ const contentLength = parseInt(res.headers.get("content-length")!);
+ if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) {
+ return null;
+ }
+ }
+ return res;
+ } catch (e) {
+ return null;
+ }
};
const genericImageHandler = async (url: URL): Promise