mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-21 18:02:33 +01:00
Actually run prettier
This commit is contained in:
parent
daeb6c3587
commit
0a8ceb9e63
@ -34,12 +34,7 @@ const addToDir = (dir) => {
|
||||
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;
|
||||
|
@ -20,9 +20,7 @@ require("module-alias/register");
|
||||
const getRouteDescriptions = require("./util/getRouteDescriptions");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const {
|
||||
NO_AUTHORIZATION_ROUTES,
|
||||
} = require("../dist/api/middlewares/Authentication");
|
||||
const { NO_AUTHORIZATION_ROUTES } = require("../dist/api/middlewares/Authentication");
|
||||
require("missing-native-js-functions");
|
||||
|
||||
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
||||
@ -35,8 +33,7 @@ let specification = {
|
||||
openapi: "3.1.0",
|
||||
info: {
|
||||
title: "Spacebar Server",
|
||||
description:
|
||||
"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
|
||||
description: "Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
|
||||
license: {
|
||||
name: "AGPLV3",
|
||||
url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
|
||||
@ -90,8 +87,7 @@ function combineSchemas(schemas) {
|
||||
continue;
|
||||
}
|
||||
specification.components = specification.components || {};
|
||||
specification.components.schemas =
|
||||
specification.components.schemas || {};
|
||||
specification.components.schemas = specification.components.schemas || {};
|
||||
specification.components.schemas[key] = definitions[key];
|
||||
delete definitions[key].additionalProperties;
|
||||
delete definitions[key].$schema;
|
||||
@ -170,8 +166,7 @@ function apiRoutes() {
|
||||
[k]: {
|
||||
...(v.body
|
||||
? {
|
||||
description:
|
||||
obj?.responses?.[k]?.description || "",
|
||||
description: obj?.responses?.[k]?.description || "",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schema,
|
||||
@ -218,12 +213,9 @@ function apiRoutes() {
|
||||
|
||||
obj.tags = [...(obj.tags || []), getTag(p)].unique();
|
||||
|
||||
specification.paths[path] = Object.assign(
|
||||
specification.paths[path] || {},
|
||||
{
|
||||
specification.paths[path] = Object.assign(specification.paths[path] || {}, {
|
||||
[method]: obj,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -236,7 +228,7 @@ function main() {
|
||||
openapiPath,
|
||||
JSON.stringify(specification, null, 4)
|
||||
.replaceAll("#/definitions", "#/components/schemas")
|
||||
.replaceAll("bigint", "number"),
|
||||
.replaceAll("bigint", "number")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -62,18 +62,13 @@ const Excluded = [
|
||||
function main() {
|
||||
const program = TJS.programFromConfig(
|
||||
path.join(__dirname, "..", "tsconfig.json"),
|
||||
walk(path.join(__dirname, "..", "src", "util", "schemas")),
|
||||
walk(path.join(__dirname, "..", "src", "util", "schemas"))
|
||||
);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
if (!generator || !program) return;
|
||||
|
||||
let schemas = generator.getUserSymbols().filter((x) => {
|
||||
return (
|
||||
(x.endsWith("Schema") ||
|
||||
x.endsWith("Response") ||
|
||||
x.startsWith("API")) &&
|
||||
!Excluded.includes(x)
|
||||
);
|
||||
return (x.endsWith("Schema") || x.endsWith("Response") || x.startsWith("API")) && !Excluded.includes(x);
|
||||
});
|
||||
|
||||
var definitions = {};
|
||||
|
@ -22,15 +22,7 @@ const path = require("path");
|
||||
(async () => {
|
||||
DataSourceOptions.setOptions({
|
||||
logging: true,
|
||||
migrations: [
|
||||
path.join(
|
||||
process.cwd(),
|
||||
"scripts",
|
||||
"stagingMigration",
|
||||
DatabaseType,
|
||||
"*.js",
|
||||
),
|
||||
],
|
||||
migrations: [path.join(process.cwd(), "scripts", "stagingMigration", DatabaseType, "*.js")],
|
||||
});
|
||||
|
||||
const dbConnection = await DataSourceOptions.initialize();
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -22,148 +22,74 @@ module.exports = class staging1672815835837 {
|
||||
name = "staging1672815835837";
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`,
|
||||
`ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`,
|
||||
`CREATE TABLE "embed_cache" ("id" character varying NOT NULL, "url" character varying NOT NULL, "embed" text NOT NULL, CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY ("id"))`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`,
|
||||
`CREATE TABLE "security_settings" ("id" character varying NOT NULL, "guild_id" character varying, "channel_id" character varying, "encryption_permission_mask" integer NOT NULL, "allowed_algorithms" text NOT NULL, "current_algorithm" character varying NOT NULL, "used_since_message" character varying, CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY ("id"))`
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "enabled" boolean NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "settingsIndex" integer`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "pub_date" TIMESTAMP NOT NULL`);
|
||||
await queryRunner.query(`UPDATE channels SET nsfw = false WHERE nsfw IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE channels SET flags = 0 WHERE flags IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "flags" SET NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "embed_cache" ("id" character varying NOT NULL, "url" character varying NOT NULL, "embed" text NOT NULL, CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY ("id"))`,
|
||||
`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "security_settings" ("id" character varying NOT NULL, "guild_id" character varying, "channel_id" character varying, "encryption_permission_mask" integer NOT NULL, "allowed_algorithms" text NOT NULL, "current_algorithm" character varying NOT NULL, "used_since_message" character varying, CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY ("id"))`,
|
||||
`ALTER TABLE "user_settings" DROP CONSTRAINT IF EXISTS "PK_e81f8bb92802737337d35c00981"`
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" ADD "index" SERIAL NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" ADD "enabled" boolean NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD "settingsIndex" integer`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" ADD "pub_date" TIMESTAMP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET nsfw = false WHERE nsfw IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET flags = 0 WHERE flags IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "channels" ALTER COLUMN "flags" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" DROP CONSTRAINT IF EXISTS "PK_e81f8bb92802737337d35c00981"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" ADD "index" SERIAL NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY ("index")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ADD "primary_category_id" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET large = false WHERE large IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET premium_tier = 0 WHERE premium_tier IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "premium_tier" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "unavailable" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET widget_enabled = false WHERE widget_enabled IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET nsfw = false WHERE nsfw IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "nsfw" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "members" ADD "premium_since" bigint`,
|
||||
`ALTER TABLE "user_settings" ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY ("index")`
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ADD "primary_category_id" character varying`);
|
||||
await queryRunner.query(`UPDATE guilds SET large = false WHERE large IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET premium_tier = 0 WHERE premium_tier IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "premium_tier" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "unavailable" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET widget_enabled = false WHERE widget_enabled IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET nsfw = false WHERE nsfw IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "nsfw" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`);
|
||||
await queryRunner.query(`ALTER TABLE "members" ADD "premium_since" bigint`);
|
||||
await queryRunner.query(`ALTER TABLE members ADD theme_colors text`);
|
||||
await queryRunner.query(`ALTER TABLE members ADD pronouns varchar`);
|
||||
await queryRunner.query(`UPDATE users SET bio = '' WHERE bio IS NULL`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE users ADD theme_colors text`);
|
||||
await queryRunner.query(`ALTER TABLE users ADD pronouns varchar`);
|
||||
await queryRunner.query(`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE users ALTER COLUMN mfa_enabled SET NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE users ALTER COLUMN mfa_enabled SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES "user_settings"("index") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "users" ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES "user_settings"("index") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ const doTimedIdentify = () =>
|
||||
token: TOKEN,
|
||||
properties: {},
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
break;
|
||||
case OPCODES.Dispatch:
|
||||
|
@ -24,9 +24,7 @@
|
||||
const { spawn } = require("child_process");
|
||||
const path = require("path");
|
||||
|
||||
const server = spawn("node", [
|
||||
path.join(__dirname, "..", "dist", "bundle", "start.js"),
|
||||
]);
|
||||
const server = spawn("node", [path.join(__dirname, "..", "dist", "bundle", "start.js")]);
|
||||
|
||||
server.stdout.on("data", (data) => {
|
||||
process.stdout.write(data);
|
||||
|
@ -15,10 +15,7 @@ let currentPath = "";
|
||||
|
||||
const proxy = (file, method, prefix, path, ...args) => {
|
||||
const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
|
||||
if (!opts)
|
||||
return console.error(
|
||||
`${file} has route without route() description middleware`,
|
||||
);
|
||||
if (!opts) return console.error(`${file} has route without route() description middleware`);
|
||||
|
||||
console.log(prefix + path + " - " + method);
|
||||
opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
|
||||
@ -26,12 +23,7 @@ const proxy = (file, method, prefix, path, ...args) => {
|
||||
};
|
||||
|
||||
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) => {
|
||||
@ -50,8 +42,7 @@ module.exports = function getRouteDescriptions() {
|
||||
currentPath = file.replace(root.slice(0, -1), "");
|
||||
currentPath = currentPath.split(".").slice(0, -1).join("."); // trancate .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
|
||||
if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
|
||||
|
||||
try {
|
||||
require(file);
|
||||
|
@ -41,13 +41,7 @@ import { initRateLimits } from "./middlewares/RateLimit";
|
||||
import { initTranslation } from "./middlewares/Translation";
|
||||
import { initInstance } from "./util/handlers/Instance";
|
||||
|
||||
const PUBLIC_ASSETS_FOLDER = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"assets",
|
||||
"public",
|
||||
);
|
||||
const PUBLIC_ASSETS_FOLDER = path.join(__dirname, "..", "..", "assets", "public");
|
||||
|
||||
export type SpacebarServerOptions = ServerOptions;
|
||||
|
||||
@ -84,16 +78,11 @@ export class SpacebarServer extends Server {
|
||||
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;
|
||||
let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
|
||||
if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
|
||||
return skip;
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -112,10 +101,7 @@ export class SpacebarServer extends Server {
|
||||
await initRateLimits(api);
|
||||
await initTranslation(api);
|
||||
|
||||
this.routes = await registerRoutes(
|
||||
this,
|
||||
path.join(__dirname, "routes", "/"),
|
||||
);
|
||||
this.routes = await registerRoutes(this, path.join(__dirname, "routes", "/"));
|
||||
|
||||
// 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
|
||||
@ -137,9 +123,7 @@ export class SpacebarServer extends Server {
|
||||
app.use("/api/v9", api);
|
||||
app.use("/api", api); // allow unversioned requests
|
||||
|
||||
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")));
|
||||
|
||||
this.app.use(ErrorHandler);
|
||||
|
||||
@ -150,8 +134,8 @@ export class SpacebarServer extends Server {
|
||||
if (logRequests)
|
||||
console.log(
|
||||
red(
|
||||
`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`,
|
||||
),
|
||||
`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`
|
||||
)
|
||||
);
|
||||
|
||||
return super.start();
|
||||
|
@ -71,11 +71,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export async function Authentication(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) {
|
||||
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 (url.startsWith("/invites") && req.method === "GET") return next();
|
||||
@ -86,8 +82,7 @@ export async function Authentication(
|
||||
})
|
||||
)
|
||||
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));
|
||||
|
||||
Sentry.setUser({ id: req.user_id });
|
||||
|
||||
|
@ -24,8 +24,7 @@ export function BodyParser(opts?: OptionsJson) {
|
||||
const jsonParser = bodyParser.json(opts);
|
||||
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.headers["content-type"])
|
||||
req.headers["content-type"] = "application/json";
|
||||
if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
|
||||
|
||||
jsonParser(req, res, (err) => {
|
||||
if (err) {
|
||||
|
@ -25,16 +25,10 @@ export function CORS(req: Request, res: Response, next: NextFunction) {
|
||||
// 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-Headers",
|
||||
req.header("Access-Control-Request-Headers") || "*",
|
||||
);
|
||||
res.set(
|
||||
"Access-Control-Allow-Methods",
|
||||
req.header("Access-Control-Request-Methods") || "*",
|
||||
"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-Headers", req.header("Access-Control-Request-Headers") || "*");
|
||||
res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*");
|
||||
|
||||
next();
|
||||
}
|
||||
|
@ -21,12 +21,7 @@ import { HTTPError } from "lambert-server";
|
||||
import { ApiError, FieldError } from "@spacebar/util";
|
||||
const EntityNotFoundErrorRegex = /"(\w+)"/;
|
||||
|
||||
export function ErrorHandler(
|
||||
error: Error & { type?: string },
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) {
|
||||
export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) {
|
||||
if (!error) return next();
|
||||
|
||||
try {
|
||||
@ -35,16 +30,13 @@ export function ErrorHandler(
|
||||
let message = error?.toString();
|
||||
let errors = undefined;
|
||||
|
||||
if (error instanceof HTTPError && error.code)
|
||||
code = httpcode = error.code;
|
||||
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`;
|
||||
message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
|
||||
code = httpcode = 404;
|
||||
} else if (error instanceof FieldError) {
|
||||
code = Number(error.code);
|
||||
@ -56,12 +48,7 @@ export function ErrorHandler(
|
||||
code = 50109;
|
||||
message = "The request body contains invalid JSON.";
|
||||
} else {
|
||||
console.error(
|
||||
`[Error] ${code} ${req.url}\n`,
|
||||
errors || error,
|
||||
"\nbody:",
|
||||
req.body,
|
||||
);
|
||||
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
|
||||
@ -75,8 +62,6 @@ export function ErrorHandler(
|
||||
res.status(httpcode).json({ code: code, message, errors });
|
||||
} catch (error) {
|
||||
console.error(`[Internal Server Error] 500`, error);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ code: 500, message: "Internal Server Error" });
|
||||
return res.status(500).json({ code: 500, message: "Internal Server Error" });
|
||||
}
|
||||
}
|
||||
|
@ -65,21 +65,14 @@ export default function rateLimit(opts: {
|
||||
if (rights.has("BYPASS_RATE_LIMITS")) return next();
|
||||
}
|
||||
|
||||
const bucket_id =
|
||||
opts.bucket ||
|
||||
req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
|
||||
const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
|
||||
let executor_id = getIpAdress(req);
|
||||
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;
|
||||
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);
|
||||
|
||||
@ -104,18 +97,13 @@ export default function rateLimit(opts: {
|
||||
}
|
||||
|
||||
res.set("X-RateLimit-Reset", `${reset}`);
|
||||
res.set(
|
||||
"X-RateLimit-Reset-After",
|
||||
`${Math.max(0, Math.ceil(resetAfterSec))}`,
|
||||
);
|
||||
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,
|
||||
);
|
||||
offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
|
||||
resetAfterMs = reset - Date.now();
|
||||
resetAfterSec = Math.ceil(resetAfterMs / 1000);
|
||||
|
||||
@ -129,10 +117,7 @@ export default function rateLimit(opts: {
|
||||
res
|
||||
.status(429)
|
||||
.set("X-RateLimit-Remaining", "0")
|
||||
.set(
|
||||
"Retry-After",
|
||||
`${Math.max(0, Math.ceil(resetAfterSec))}`,
|
||||
)
|
||||
.set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
|
||||
// TODO: error rate limit message translation
|
||||
.send({
|
||||
message: "You are being rate limited.",
|
||||
@ -156,11 +141,7 @@ export default function rateLimit(opts: {
|
||||
// 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
|
||||
) {
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
|
||||
return hitRoute(hitRouteOpts);
|
||||
}
|
||||
});
|
||||
@ -198,7 +179,7 @@ export async function initRateLimits(app: Router) {
|
||||
bucket: "global",
|
||||
onlyIp: true,
|
||||
...ip,
|
||||
}),
|
||||
})
|
||||
);
|
||||
app.use(rateLimit({ bucket: "global", ...global }));
|
||||
app.use(
|
||||
@ -207,24 +188,16 @@ export async function initRateLimits(app: Router) {
|
||||
error: true,
|
||||
onlyIp: true,
|
||||
...error,
|
||||
}),
|
||||
})
|
||||
);
|
||||
app.use("/guilds/:id", rateLimit(routes.guild));
|
||||
app.use("/webhooks/:id", rateLimit(routes.webhook));
|
||||
app.use("/channels/: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("/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;
|
||||
}) {
|
||||
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) {
|
||||
|
@ -27,12 +27,8 @@ 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 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)
|
||||
@ -43,9 +39,7 @@ export async function initTranslation(router: Router) {
|
||||
fallbackLng: "en",
|
||||
ns,
|
||||
backend: {
|
||||
loadPath:
|
||||
path.join(ASSET_FOLDER_PATH, "locales") +
|
||||
"/{{lng}}/{{ns}}.json",
|
||||
loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
|
||||
},
|
||||
load: "all",
|
||||
});
|
||||
|
@ -50,15 +50,14 @@ router.post(
|
||||
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);
|
||||
|
||||
res.send({
|
||||
token: await generateToken(user.id),
|
||||
}).status(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -77,13 +76,9 @@ router.post(
|
||||
const bot = await User.findOneOrFail({ where: { id: req.params.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))
|
||||
)
|
||||
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() };
|
||||
@ -93,7 +88,7 @@ router.post(
|
||||
const token = await generateToken(bot.id);
|
||||
|
||||
res.json({ token }).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -120,14 +115,9 @@ router.patch(
|
||||
|
||||
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);
|
||||
|
||||
@ -135,7 +125,7 @@ router.patch(
|
||||
|
||||
await app.save();
|
||||
res.json(app).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -34,7 +34,7 @@ router.get(
|
||||
// TODO:
|
||||
//const { exclude_consumed } = req.query;
|
||||
res.status(200).send([]);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,11 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Application,
|
||||
ApplicationModifySchema,
|
||||
DiscordApiErrors,
|
||||
} from "@spacebar/util";
|
||||
import { Application, ApplicationModifySchema, DiscordApiErrors } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { verifyToken } from "node-2fa";
|
||||
@ -45,11 +41,10 @@ router.get(
|
||||
where: { id: req.params.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;
|
||||
|
||||
return res.json(app);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -73,14 +68,9 @@ router.patch(
|
||||
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))
|
||||
)
|
||||
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) {
|
||||
@ -93,7 +83,7 @@ router.patch(
|
||||
await app.save();
|
||||
|
||||
return res.json(app);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -111,20 +101,15 @@ router.post(
|
||||
where: { id: req.params.id },
|
||||
relations: ["bot", "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;
|
||||
|
||||
if (
|
||||
app.owner.totp_secret &&
|
||||
(!req.body.code ||
|
||||
verifyToken(app.owner.totp_secret, req.body.code))
|
||||
)
|
||||
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);
|
||||
|
||||
await Application.delete({ id: app.id });
|
||||
|
||||
res.send().status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -32,7 +32,7 @@ router.get(
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
res.json([]).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -33,7 +33,7 @@ router.get(
|
||||
async (req: Request, res: Response) => {
|
||||
//TODO
|
||||
res.send([]).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,14 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Application,
|
||||
ApplicationCreateSchema,
|
||||
Config,
|
||||
User,
|
||||
createAppBotUser,
|
||||
trimSpecial,
|
||||
} from "@spacebar/util";
|
||||
import { Application, ApplicationCreateSchema, Config, User, createAppBotUser, trimSpecial } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router: Router = Router();
|
||||
@ -44,7 +37,7 @@ router.get(
|
||||
relations: ["owner", "bot"],
|
||||
});
|
||||
res.json(results).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -77,7 +70,7 @@ router.post(
|
||||
} else await app.save();
|
||||
|
||||
res.json(app);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,13 +17,7 @@
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||
import {
|
||||
Config,
|
||||
Email,
|
||||
FieldErrors,
|
||||
ForgotPasswordSchema,
|
||||
User,
|
||||
} from "@spacebar/util";
|
||||
import { Config, Email, FieldErrors, ForgotPasswordSchema, User } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
const router = Router();
|
||||
@ -47,10 +41,7 @@ router.post(
|
||||
|
||||
const config = Config.get();
|
||||
|
||||
if (
|
||||
config.passwordReset.requireCaptcha &&
|
||||
config.security.captcha.enabled
|
||||
) {
|
||||
if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
|
||||
const { sitekey, service } = config.security.captcha;
|
||||
if (!captcha_key) {
|
||||
return res.status(400).json({
|
||||
@ -87,8 +78,7 @@ router.post(
|
||||
if (!user.email)
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
message:
|
||||
"This account does not have an email address associated with it.",
|
||||
message: "This account does not have an email address associated with it.",
|
||||
code: "NO_EMAIL",
|
||||
},
|
||||
});
|
||||
@ -110,12 +100,10 @@ router.post(
|
||||
return res.sendStatus(204);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`,
|
||||
);
|
||||
console.error(`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`);
|
||||
throw new HTTPError("Failed to send password reset email", 500);
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -29,13 +29,11 @@ router.get(
|
||||
query: {
|
||||
count: {
|
||||
type: "number",
|
||||
description:
|
||||
"The number of registration tokens to generate. Defaults to 1.",
|
||||
description: "The number of registration tokens to generate. Defaults to 1.",
|
||||
},
|
||||
length: {
|
||||
type: "number",
|
||||
description:
|
||||
"The length of each registration token. Defaults to 255.",
|
||||
description: "The length of each registration token. Defaults to 255.",
|
||||
},
|
||||
},
|
||||
right: "OPERATOR",
|
||||
@ -43,18 +41,14 @@ router.get(
|
||||
}),
|
||||
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 length = req.query.length ? parseInt(req.query.length as string) : 255;
|
||||
|
||||
const tokens: ValidRegistrationToken[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const token = ValidRegistrationToken.create({
|
||||
token: random(length),
|
||||
expires_at:
|
||||
Date.now() +
|
||||
Config.get().security.defaultRegistrationTokenExpiration,
|
||||
expires_at: Date.now() + Config.get().security.defaultRegistrationTokenExpiration,
|
||||
});
|
||||
tokens.push(token);
|
||||
}
|
||||
@ -67,16 +61,11 @@ router.get(
|
||||
});
|
||||
|
||||
const ret = req.query.include_url
|
||||
? tokens.map(
|
||||
(x) =>
|
||||
`${Config.get().general.frontPage}/register?token=${
|
||||
x.token
|
||||
}`,
|
||||
)
|
||||
? 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"));
|
||||
|
||||
return res.json({ tokens: ret });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ router.get(
|
||||
country_code: country_code,
|
||||
promotional_email_opt_in: { required: true, pre_checked: false },
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -47,8 +47,7 @@ router.post(
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { login, password, captcha_key, undelete } =
|
||||
req.body as LoginSchema;
|
||||
const { login, password, captcha_key, undelete } = req.body as LoginSchema;
|
||||
|
||||
const config = Config.get();
|
||||
|
||||
@ -101,10 +100,7 @@ router.post(
|
||||
});
|
||||
|
||||
// the salt is saved in the password refer to bcrypt docs
|
||||
const same_password = await bcrypt.compare(
|
||||
password,
|
||||
user.data.hash || "",
|
||||
);
|
||||
const same_password = await bcrypt.compare(password, user.data.hash || "");
|
||||
if (!same_password) {
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
@ -123,8 +119,7 @@ router.post(
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
|
||||
message:
|
||||
"Email verification is required, please check your email.",
|
||||
message: "Email verification is required, please check your email.",
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -153,9 +148,7 @@ router.post(
|
||||
const challenge = JSON.stringify({
|
||||
publicKey: {
|
||||
...options,
|
||||
challenge: Buffer.from(options.challenge).toString(
|
||||
"base64",
|
||||
),
|
||||
challenge: Buffer.from(options.challenge).toString("base64"),
|
||||
allowCredentials: user.security_keys.map((x) => ({
|
||||
id: x.key_id,
|
||||
type: "public-key",
|
||||
@ -179,10 +172,8 @@ router.post(
|
||||
|
||||
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 });
|
||||
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({
|
||||
@ -203,7 +194,7 @@ router.post(
|
||||
// https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
|
||||
|
||||
res.json({ token, settings: { ...user.settings, index: undefined } });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -31,16 +31,12 @@ router.post(
|
||||
}),
|
||||
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,
|
||||
);
|
||||
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 (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
|
||||
}
|
||||
res.status(204).send();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -59,11 +59,7 @@ router.post(
|
||||
|
||||
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,
|
||||
);
|
||||
if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
} else {
|
||||
backup.consumed = true;
|
||||
await backup.save();
|
||||
@ -75,7 +71,7 @@ router.post(
|
||||
token: await generateToken(user.id),
|
||||
settings: { ...user.settings, index: undefined },
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,14 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
generateToken,
|
||||
SecurityKey,
|
||||
User,
|
||||
verifyWebAuthnToken,
|
||||
WebAuthn,
|
||||
WebAuthnTotpSchema,
|
||||
} from "@spacebar/util";
|
||||
import { generateToken, SecurityKey, User, verifyWebAuthnToken, WebAuthn, WebAuthnTotpSchema } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { ExpectedAssertionResult } from "fido2-lib";
|
||||
import { HTTPError } from "lambert-server";
|
||||
@ -65,46 +58,33 @@ router.post(
|
||||
});
|
||||
|
||||
const ret = await verifyWebAuthnToken(ticket);
|
||||
if (!ret)
|
||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
|
||||
await User.update({ id: user.id }, { totp_last_ticket: "" });
|
||||
|
||||
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"),
|
||||
key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"),
|
||||
},
|
||||
});
|
||||
|
||||
const assertionExpectations: ExpectedAssertionResult = JSON.parse(
|
||||
Buffer.from(
|
||||
clientAttestationResponse.response.clientDataJSON,
|
||||
"base64",
|
||||
).toString(),
|
||||
Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()
|
||||
);
|
||||
|
||||
const authnResult = await WebAuthn.fido2.assertionResult(
|
||||
clientAttestationResponse,
|
||||
{
|
||||
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");
|
||||
|
||||
@ -116,7 +96,7 @@ router.post(
|
||||
token: await generateToken(user.id),
|
||||
user_settings: user.settings,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -16,13 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
IPAnalysis,
|
||||
getIpAdress,
|
||||
isProxy,
|
||||
route,
|
||||
verifyCaptcha,
|
||||
} from "@spacebar/api";
|
||||
import { IPAnalysis, getIpAdress, isProxy, route, verifyCaptcha } from "@spacebar/api";
|
||||
import {
|
||||
Config,
|
||||
FieldErrors,
|
||||
@ -65,13 +59,9 @@ router.post(
|
||||
});
|
||||
await regToken.remove();
|
||||
regTokenUsed = true;
|
||||
console.log(
|
||||
`[REGISTER] Registration token ${token} used for registration!`,
|
||||
);
|
||||
console.log(`[REGISTER] Registration token ${token} used for registration!`);
|
||||
} else {
|
||||
console.log(
|
||||
`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`,
|
||||
);
|
||||
console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,11 +94,7 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!regTokenUsed &&
|
||||
register.requireCaptcha &&
|
||||
security.captcha.enabled
|
||||
) {
|
||||
if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
|
||||
const { sitekey, service } = security.captcha;
|
||||
if (!body.captcha_key) {
|
||||
return res?.status(400).json({
|
||||
@ -139,9 +125,7 @@ router.post(
|
||||
throw FieldErrors({
|
||||
email: {
|
||||
code: "EMAIL_ALREADY_REGISTERED",
|
||||
message: req.t(
|
||||
"auth:register.EMAIL_ALREADY_REGISTERED",
|
||||
),
|
||||
message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -176,9 +160,7 @@ router.post(
|
||||
throw FieldErrors({
|
||||
email: {
|
||||
code: "EMAIL_ALREADY_REGISTERED",
|
||||
message: req.t(
|
||||
"auth:register.EMAIL_ALREADY_REGISTERED",
|
||||
),
|
||||
message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -198,14 +180,9 @@ router.post(
|
||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||
},
|
||||
});
|
||||
} else if (
|
||||
register.dateOfBirth.required &&
|
||||
register.dateOfBirth.minimum
|
||||
) {
|
||||
} else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
|
||||
const minimum = new Date();
|
||||
minimum.setFullYear(
|
||||
minimum.getFullYear() - register.dateOfBirth.minimum,
|
||||
);
|
||||
minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
|
||||
body.date_of_birth = new Date(body.date_of_birth as Date);
|
||||
|
||||
// higher is younger
|
||||
@ -228,10 +205,7 @@ router.post(
|
||||
throw FieldErrors({
|
||||
password: {
|
||||
code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
|
||||
message: req.t(
|
||||
"auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH",
|
||||
{ min: min },
|
||||
),
|
||||
message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }),
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -249,8 +223,7 @@ router.post(
|
||||
if (
|
||||
!regTokenUsed &&
|
||||
!body.invite &&
|
||||
(register.requireInvite ||
|
||||
(register.guestsRequireInvite && !register.email))
|
||||
(register.requireInvite || (register.guestsRequireInvite && !register.email))
|
||||
) {
|
||||
// require invite to register -> e.g. for organizations to send invites to their employees
|
||||
throw FieldErrors({
|
||||
@ -266,18 +239,14 @@ router.post(
|
||||
limits.absoluteRate.register.enabled &&
|
||||
(await User.count({
|
||||
where: {
|
||||
created_at: MoreThan(
|
||||
new Date(
|
||||
Date.now() - limits.absoluteRate.register.window,
|
||||
),
|
||||
),
|
||||
created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)),
|
||||
},
|
||||
})) >= limits.absoluteRate.register.limit
|
||||
) {
|
||||
console.log(
|
||||
`Global register ratelimit exceeded for ${getIpAdress(req)}, ${
|
||||
req.body.username
|
||||
}, ${req.body.invite || "No invite given"}`,
|
||||
`Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${
|
||||
req.body.invite || "No invite given"
|
||||
}`
|
||||
);
|
||||
throw FieldErrors({
|
||||
email: {
|
||||
@ -295,7 +264,7 @@ router.post(
|
||||
}
|
||||
|
||||
return res.json({ token: await generateToken(user.id) });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,14 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
checkToken,
|
||||
Email,
|
||||
FieldErrors,
|
||||
generateToken,
|
||||
PasswordResetSchema,
|
||||
User,
|
||||
} from "@spacebar/util";
|
||||
import { checkToken, Email, FieldErrors, generateToken, PasswordResetSchema, User } from "@spacebar/util";
|
||||
import bcrypt from "bcrypt";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
@ -76,7 +69,7 @@ router.post(
|
||||
await Email.sendPasswordChanged(user, user.email!);
|
||||
|
||||
res.json({ token: await generateToken(user.id) });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,13 +17,7 @@
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||
import {
|
||||
checkToken,
|
||||
Config,
|
||||
FieldErrors,
|
||||
generateToken,
|
||||
User,
|
||||
} from "@spacebar/util";
|
||||
import { checkToken, Config, FieldErrors, generateToken, User } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router();
|
||||
|
||||
@ -97,7 +91,7 @@ router.post(
|
||||
await User.update({ id: user.id }, { verified: true });
|
||||
|
||||
return res.json(await getToken(user));
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -52,12 +52,10 @@ router.post(
|
||||
return res.sendStatus(204);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
`Failed to send verification email to ${user.username}#${user.discriminator}: ${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;
|
||||
|
@ -52,7 +52,7 @@ router.post(
|
||||
nonce: "NoncePlaceholder",
|
||||
regenerate_nonce: "RegenNoncePlaceholder",
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -52,7 +52,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.send(channel);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -101,7 +101,7 @@ router.delete(
|
||||
}
|
||||
|
||||
res.send(channel);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -122,11 +122,7 @@ router.patch(
|
||||
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,
|
||||
);
|
||||
if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
|
||||
|
||||
const channel = await Channel.findOneOrFail({
|
||||
where: { id: channel_id },
|
||||
@ -143,7 +139,7 @@ router.patch(
|
||||
]);
|
||||
|
||||
res.send(channel);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -65,9 +65,7 @@ router.post(
|
||||
const { guild_id } = channel;
|
||||
|
||||
const expires_at =
|
||||
body.max_age == 0 || body.max_age == undefined
|
||||
? undefined
|
||||
: new Date(body.max_age * 1000 + Date.now());
|
||||
body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now());
|
||||
|
||||
const invite = await Invite.create({
|
||||
code: random(),
|
||||
@ -95,7 +93,7 @@ router.post(
|
||||
} as InviteCreateEvent);
|
||||
|
||||
res.status(201).send(data);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
@ -126,7 +124,7 @@ router.get(
|
||||
});
|
||||
|
||||
res.status(200).send(invites);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,12 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
emitEvent,
|
||||
getPermission,
|
||||
MessageAckEvent,
|
||||
ReadState,
|
||||
} from "@spacebar/util";
|
||||
import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
@ -43,18 +38,13 @@ router.post(
|
||||
async (req: Request, res: Response) => {
|
||||
const { channel_id, message_id } = req.params;
|
||||
|
||||
const permission = await getPermission(
|
||||
req.user_id,
|
||||
undefined,
|
||||
channel_id,
|
||||
);
|
||||
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 });
|
||||
if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
|
||||
read_state.last_message_id = message_id;
|
||||
|
||||
await read_state.save();
|
||||
@ -70,7 +60,7 @@ router.post(
|
||||
} as MessageAckEvent);
|
||||
|
||||
res.json({ token: null });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,7 +57,7 @@ router.post(
|
||||
flags: 1,
|
||||
components: [],
|
||||
}).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -75,11 +75,7 @@ router.patch(
|
||||
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);
|
||||
|
||||
@ -139,7 +135,7 @@ router.patch(
|
||||
// these are not in the Discord.com response
|
||||
mention_channels: new_message.mention_channels,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Backfill message with specific timestamp
|
||||
@ -196,13 +192,8 @@ router.put(
|
||||
|
||||
if (req.file) {
|
||||
try {
|
||||
const file = await uploadFile(
|
||||
`/attachments/${req.params.channel_id}`,
|
||||
req.file,
|
||||
);
|
||||
attachments.push(
|
||||
Attachment.create({ ...file, proxy_url: file.url }),
|
||||
);
|
||||
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);
|
||||
}
|
||||
@ -241,12 +232,10 @@ router.put(
|
||||
]);
|
||||
|
||||
// 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),
|
||||
);
|
||||
postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
|
||||
|
||||
return res.json(message);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
@ -272,17 +261,12 @@ router.get(
|
||||
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);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -310,11 +294,7 @@ router.delete(
|
||||
|
||||
if (message.author_id !== req.user_id) {
|
||||
if (!rights.has("MANAGE_MESSAGES")) {
|
||||
const permission = await getPermission(
|
||||
req.user_id,
|
||||
channel.guild_id,
|
||||
channel_id,
|
||||
);
|
||||
const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
|
||||
permission.hasThrow("MANAGE_MESSAGES");
|
||||
}
|
||||
} else rights.hasThrow("SELF_DELETE_MESSAGES");
|
||||
@ -332,7 +312,7 @@ router.delete(
|
||||
} as MessageDeleteEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -88,7 +88,7 @@ router.delete(
|
||||
} as MessageReactionRemoveAllEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -113,9 +113,7 @@ router.delete(
|
||||
});
|
||||
|
||||
const already_added = message.reactions.find(
|
||||
(x) =>
|
||||
(x.emoji.id === emoji.id && emoji.id) ||
|
||||
x.emoji.name === emoji.name,
|
||||
(x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
|
||||
);
|
||||
if (!already_added) throw new HTTPError("Reaction not found", 404);
|
||||
message.reactions.remove(already_added);
|
||||
@ -135,7 +133,7 @@ router.delete(
|
||||
]);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
@ -161,9 +159,7 @@ router.get(
|
||||
where: { id: message_id, channel_id },
|
||||
});
|
||||
const reaction = message.reactions.find(
|
||||
(x) =>
|
||||
(x.emoji.id === emoji.id && emoji.id) ||
|
||||
x.emoji.name === emoji.name,
|
||||
(x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
|
||||
);
|
||||
if (!reaction) throw new HTTPError("Reaction not found", 404);
|
||||
|
||||
@ -175,7 +171,7 @@ router.get(
|
||||
});
|
||||
|
||||
res.json(users);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.put(
|
||||
@ -204,9 +200,7 @@ router.put(
|
||||
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,
|
||||
(x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
|
||||
);
|
||||
|
||||
if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
|
||||
@ -222,8 +216,7 @@ router.put(
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
@ -258,7 +251,7 @@ router.put(
|
||||
} as MessageReactionAddEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -288,30 +281,19 @@ router.delete(
|
||||
|
||||
if (user_id === "@me") user_id = req.user_id;
|
||||
else {
|
||||
const permissions = await getPermission(
|
||||
req.user_id,
|
||||
undefined,
|
||||
channel_id,
|
||||
);
|
||||
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,
|
||||
(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);
|
||||
if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
|
||||
|
||||
already_added.count--;
|
||||
|
||||
if (already_added.count <= 0) message.reactions.remove(already_added);
|
||||
else
|
||||
already_added.user_ids.splice(
|
||||
already_added.user_ids.indexOf(user_id),
|
||||
1,
|
||||
);
|
||||
else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
|
||||
|
||||
await message.save();
|
||||
|
||||
@ -328,7 +310,7 @@ router.delete(
|
||||
} as MessageReactionRemoveEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,15 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Channel,
|
||||
Config,
|
||||
emitEvent,
|
||||
getPermission,
|
||||
getRights,
|
||||
Message,
|
||||
MessageDeleteBulkEvent,
|
||||
} from "@spacebar/util";
|
||||
import { Channel, Config, emitEvent, getPermission, getRights, Message, MessageDeleteBulkEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
||||
@ -54,30 +46,22 @@ router.post(
|
||||
const channel = await Channel.findOneOrFail({
|
||||
where: { id: channel_id },
|
||||
});
|
||||
if (!channel.guild_id)
|
||||
throw new HTTPError("Can't bulk delete dm channel messages", 400);
|
||||
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 superuser = rights.has("MANAGE_MESSAGES");
|
||||
const permission = await getPermission(
|
||||
req.user_id,
|
||||
channel?.guild_id,
|
||||
channel_id,
|
||||
);
|
||||
const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
|
||||
|
||||
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 (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`,
|
||||
);
|
||||
throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
|
||||
}
|
||||
|
||||
await Message.delete(messages);
|
||||
@ -89,5 +73,5 @@ router.post(
|
||||
} as MessageDeleteBulkEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -40,13 +40,7 @@ import {
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import multer from "multer";
|
||||
import {
|
||||
FindManyOptions,
|
||||
FindOperator,
|
||||
LessThan,
|
||||
MoreThan,
|
||||
MoreThanOrEqual,
|
||||
} from "typeorm";
|
||||
import { FindManyOptions, FindOperator, LessThan, MoreThan, MoreThanOrEqual } from "typeorm";
|
||||
import { URL } from "url";
|
||||
|
||||
const router: Router = Router();
|
||||
@ -68,8 +62,7 @@ router.get(
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description:
|
||||
"max number of messages to return (1-100). defaults to 50",
|
||||
description: "max number of messages to return (1-100). defaults to 50",
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
@ -95,14 +88,9 @@ router.get(
|
||||
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);
|
||||
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,
|
||||
);
|
||||
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([]);
|
||||
|
||||
@ -148,12 +136,10 @@ router.get(
|
||||
}
|
||||
} else {
|
||||
if (after) {
|
||||
if (BigInt(after) > BigInt(Snowflake.generate()))
|
||||
return res.status(422);
|
||||
if (BigInt(after) > BigInt(Snowflake.generate())) return res.status(422);
|
||||
query.where.id = MoreThan(after);
|
||||
} else if (before) {
|
||||
if (BigInt(before) > BigInt(Snowflake.generate()))
|
||||
return res.status(422);
|
||||
if (BigInt(before) > BigInt(Snowflake.generate())) return res.status(422);
|
||||
query.where.id = LessThan(before);
|
||||
}
|
||||
|
||||
@ -180,12 +166,8 @@ router.get(
|
||||
});
|
||||
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}`;
|
||||
y.proxy_url = `${endpoint == null ? "" : endpoint}${
|
||||
new URL(uri).pathname
|
||||
}`;
|
||||
const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
|
||||
y.proxy_url = `${endpoint == null ? "" : endpoint}${new URL(uri).pathname}`;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -202,7 +184,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(ret);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: config max upload size
|
||||
@ -258,10 +240,7 @@ router.post(
|
||||
relations: ["recipients", "recipients.user"],
|
||||
});
|
||||
if (!channel.isWritable()) {
|
||||
throw new HTTPError(
|
||||
`Cannot send messages to channel of type ${channel.type}`,
|
||||
400,
|
||||
);
|
||||
throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
|
||||
}
|
||||
|
||||
if (body.nonce) {
|
||||
@ -283,12 +262,7 @@ router.post(
|
||||
const count = await Message.count({
|
||||
where: {
|
||||
channel_id,
|
||||
timestamp: MoreThan(
|
||||
new Date(
|
||||
Date.now() -
|
||||
limits.absoluteRate.sendMessage.window,
|
||||
),
|
||||
),
|
||||
timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
|
||||
},
|
||||
});
|
||||
|
||||
@ -305,13 +279,8 @@ router.post(
|
||||
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 }),
|
||||
);
|
||||
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() });
|
||||
}
|
||||
@ -347,14 +316,12 @@ router.post(
|
||||
recipient.save(),
|
||||
emitEvent({
|
||||
event: "CHANNEL_CREATE",
|
||||
data: channel_dto.excludedRecipients([
|
||||
recipient.user_id,
|
||||
]),
|
||||
data: channel_dto.excludedRecipients([recipient.user_id]),
|
||||
user_id: recipient.user_id,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}) || [],
|
||||
}) || []
|
||||
);
|
||||
}
|
||||
|
||||
@ -370,16 +337,13 @@ router.post(
|
||||
|
||||
// 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);
|
||||
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 });
|
||||
if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
|
||||
read_state.last_message_id = message.id;
|
||||
|
||||
await Promise.all([
|
||||
@ -391,21 +355,16 @@ router.post(
|
||||
data: message,
|
||||
} as MessageCreateEvent),
|
||||
message.guild_id
|
||||
? Member.update(
|
||||
{ id: req.user_id, guild_id: message.guild_id },
|
||||
{ last_message_id: message.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),
|
||||
);
|
||||
postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
|
||||
|
||||
return res.json(message);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -55,15 +55,14 @@ router.put(
|
||||
if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
|
||||
|
||||
if (body.type === 0) {
|
||||
if (!(await Role.count({ where: { id: overwrite_id } })))
|
||||
throw new HTTPError("role not found", 404);
|
||||
if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
|
||||
} else if (body.type === 1) {
|
||||
if (!(await Member.count({ where: { id: overwrite_id } })))
|
||||
throw new HTTPError("user not found", 404);
|
||||
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);
|
||||
let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find(
|
||||
(x) => x.id === overwrite_id
|
||||
);
|
||||
if (!overwrite) {
|
||||
overwrite = {
|
||||
id: overwrite_id,
|
||||
@ -73,14 +72,8 @@ router.put(
|
||||
};
|
||||
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")),
|
||||
);
|
||||
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(),
|
||||
@ -92,7 +85,7 @@ router.put(
|
||||
]);
|
||||
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: check permission hierarchy
|
||||
@ -107,9 +100,7 @@ router.delete(
|
||||
});
|
||||
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(),
|
||||
@ -121,7 +112,7 @@ router.delete(
|
||||
]);
|
||||
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,8 +57,7 @@ router.put(
|
||||
where: { channel: { id: channel_id }, pinned: true },
|
||||
});
|
||||
const { maxPins } = Config.get().limits.channel;
|
||||
if (pinned_count >= maxPins)
|
||||
throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
|
||||
if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
|
||||
|
||||
await Promise.all([
|
||||
Message.update({ id: message_id }, { pinned: true }),
|
||||
@ -79,7 +78,7 @@ router.put(
|
||||
]);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -129,7 +128,7 @@ router.delete(
|
||||
]);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
@ -153,7 +152,7 @@ router.get(
|
||||
});
|
||||
|
||||
res.send(pins);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,17 +57,12 @@ router.post(
|
||||
where: { id: channel_id },
|
||||
});
|
||||
|
||||
if (!channel.guild_id)
|
||||
throw new HTTPError("Can't purge dm channels", 400);
|
||||
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,
|
||||
);
|
||||
const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
|
||||
permissions.hasThrow("MANAGE_MESSAGES");
|
||||
permissions.hasThrow("MANAGE_CHANNELS");
|
||||
}
|
||||
@ -84,9 +79,7 @@ router.post(
|
||||
where: {
|
||||
channel_id,
|
||||
id: Between(after, before), // the right way around
|
||||
author_id: rights.has("SELF_DELETE_MESSAGES")
|
||||
? undefined
|
||||
: Not(req.user_id),
|
||||
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: [
|
||||
@ -121,5 +114,5 @@ router.post(
|
||||
} as MessageDeleteBulkEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -48,24 +48,16 @@ router.put(
|
||||
});
|
||||
|
||||
if (channel.type !== ChannelType.GROUP_DM) {
|
||||
const recipients = [
|
||||
...(channel.recipients?.map((r) => r.user_id) || []),
|
||||
user_id,
|
||||
].unique();
|
||||
const recipients = [...(channel.recipients?.map((r) => r.user_id) || []), user_id].unique();
|
||||
|
||||
const new_channel = await Channel.createDMChannel(
|
||||
recipients,
|
||||
req.user_id,
|
||||
);
|
||||
const new_channel = await Channel.createDMChannel(recipients, req.user_id);
|
||||
return res.status(201).json(new_channel);
|
||||
} else {
|
||||
if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
|
||||
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
|
||||
}
|
||||
|
||||
channel.recipients?.push(
|
||||
Recipient.create({ channel_id: channel_id, user_id: user_id }),
|
||||
);
|
||||
channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
|
||||
await channel.save();
|
||||
|
||||
await emitEvent({
|
||||
@ -87,7 +79,7 @@ router.put(
|
||||
} as ChannelRecipientAddEvent);
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -104,12 +96,7 @@ router.delete(
|
||||
where: { id: channel_id },
|
||||
relations: ["recipients"],
|
||||
});
|
||||
if (
|
||||
!(
|
||||
channel.type === ChannelType.GROUP_DM &&
|
||||
(channel.owner_id === req.user_id || user_id === req.user_id)
|
||||
)
|
||||
)
|
||||
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)) {
|
||||
@ -119,7 +106,7 @@ router.delete(
|
||||
await Channel.removeRecipientFromChannel(channel, user_id);
|
||||
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -64,7 +64,7 @@ router.post(
|
||||
} as TypingStartEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -47,7 +47,7 @@ router.get(
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
res.json([]);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: use Image Data Type for avatar instead of String
|
||||
@ -77,8 +77,7 @@ router.post(
|
||||
|
||||
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);
|
||||
if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
|
||||
|
||||
let { avatar, name } = req.body as WebhookCreateSchema;
|
||||
name = trimSpecial(name);
|
||||
@ -105,7 +104,7 @@ router.post(
|
||||
...hook,
|
||||
user: user,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -30,9 +30,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
provider_id: {
|
||||
code: "BASE_TYPE_CHOICES",
|
||||
message: req.t("common:field.BASE_TYPE_CHOICES", {
|
||||
types: Array.from(ConnectionStore.connections.keys()).join(
|
||||
", ",
|
||||
),
|
||||
types: Array.from(ConnectionStore.connections.keys()).join(", "),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
@ -17,20 +17,12 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
ConnectionCallbackSchema,
|
||||
ConnectionStore,
|
||||
emitEvent,
|
||||
FieldErrors,
|
||||
} from "@spacebar/util";
|
||||
import { ConnectionCallbackSchema, ConnectionStore, emitEvent, FieldErrors } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({ requestBody: "ConnectionCallbackSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
router.post("/", route({ requestBody: "ConnectionCallbackSchema" }), async (req: Request, res: Response) => {
|
||||
const { connection_name } = req.params;
|
||||
const connection = ConnectionStore.connections.get(connection_name);
|
||||
if (!connection)
|
||||
@ -38,9 +30,7 @@ router.post(
|
||||
provider_id: {
|
||||
code: "BASE_TYPE_CHOICES",
|
||||
message: req.t("common:field.BASE_TYPE_CHOICES", {
|
||||
types: Array.from(
|
||||
ConnectionStore.connections.keys(),
|
||||
).join(", "),
|
||||
types: Array.from(ConnectionStore.connections.keys()).join(", "),
|
||||
}),
|
||||
},
|
||||
});
|
||||
@ -65,7 +55,6 @@ router.post(
|
||||
});
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -70,7 +70,7 @@ router.get(
|
||||
offset: Number(offset || Config.get().guild.discovery.offset),
|
||||
limit: Number(limit || configLimit),
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -38,12 +38,10 @@ router.get(
|
||||
// const { locale, primary_only } = req.query;
|
||||
const { primary_only } = req.query;
|
||||
|
||||
const out = primary_only
|
||||
? await Categories.find()
|
||||
: await Categories.find({ where: { is_primary: true } });
|
||||
const out = primary_only ? await Categories.find() : await Categories.find({ where: { is_primary: true } });
|
||||
|
||||
res.send(out);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -52,7 +52,7 @@ router.get(
|
||||
});
|
||||
|
||||
res.redirect(release.url);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -43,7 +43,7 @@ router.get(
|
||||
max_concurrency: 1,
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -36,7 +36,7 @@ router.get(
|
||||
res.json({
|
||||
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -36,9 +36,7 @@ router.get(
|
||||
media_format: {
|
||||
type: "string",
|
||||
description: "Media format",
|
||||
values: Object.keys(TenorMediaTypes).filter((key) =>
|
||||
isNaN(Number(key)),
|
||||
),
|
||||
values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
|
||||
},
|
||||
locale: {
|
||||
type: "string",
|
||||
@ -65,13 +63,13 @@ router.get(
|
||||
agent,
|
||||
method: "get",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { results } = await response.json();
|
||||
|
||||
res.json(results.map(parseGifResult)).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -31,9 +31,7 @@ router.get(
|
||||
media_format: {
|
||||
type: "string",
|
||||
description: "Media format",
|
||||
values: Object.keys(TenorMediaTypes).filter((key) =>
|
||||
isNaN(Number(key)),
|
||||
),
|
||||
values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
|
||||
},
|
||||
locale: {
|
||||
type: "string",
|
||||
@ -60,13 +58,13 @@ router.get(
|
||||
agent,
|
||||
method: "get",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { results } = await response.json();
|
||||
|
||||
res.json(results.map(parseGifResult)).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,12 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
TenorCategoriesResults,
|
||||
TenorTrendingResults,
|
||||
getGifApiKey,
|
||||
parseGifResult,
|
||||
} from "@spacebar/util";
|
||||
import { TenorCategoriesResults, TenorTrendingResults, getGifApiKey, parseGifResult } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import fetch from "node-fetch";
|
||||
import { ProxyAgent } from "proxy-agent";
|
||||
@ -55,28 +50,20 @@ router.get(
|
||||
const agent = new ProxyAgent();
|
||||
|
||||
const [responseSource, trendGifSource] = await Promise.all([
|
||||
fetch(
|
||||
`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
|
||||
{
|
||||
fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
|
||||
agent,
|
||||
method: "get",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
),
|
||||
fetch(
|
||||
`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
|
||||
{
|
||||
}),
|
||||
fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
|
||||
agent,
|
||||
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) => ({
|
||||
@ -85,7 +72,7 @@ router.get(
|
||||
})),
|
||||
gifs: [parseGifResult(results[0])],
|
||||
}).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -39,9 +39,7 @@ router.get(
|
||||
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
|
||||
|
||||
const genLoadId = (size: number) =>
|
||||
[...Array(size)]
|
||||
.map(() => Math.floor(Math.random() * 16).toString(16))
|
||||
.join("");
|
||||
[...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
|
||||
|
||||
const guilds = showAllGuilds
|
||||
? await Guild.find({ take: Math.abs(Number(limit || 24)) })
|
||||
@ -53,7 +51,7 @@ router.get(
|
||||
recommended_guilds: guilds,
|
||||
load_id: `server_recs/${genLoadId(32)}`,
|
||||
}).status(200);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -78,7 +78,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(bansObj);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
@ -115,7 +115,7 @@ router.get(
|
||||
delete ban.ip;
|
||||
|
||||
return res.json(ban);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.put(
|
||||
@ -139,14 +139,8 @@ router.put(
|
||||
const { guild_id } = req.params;
|
||||
const banned_user_id = req.params.user_id;
|
||||
|
||||
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);
|
||||
@ -175,7 +169,7 @@ router.put(
|
||||
]);
|
||||
|
||||
return res.json(ban);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.put(
|
||||
@ -200,10 +194,7 @@ router.put(
|
||||
const banned_user = await User.getPublicUser(req.params.user_id);
|
||||
|
||||
if (req.permission?.cache.guild?.owner_id === req.params.user_id)
|
||||
throw new HTTPError(
|
||||
"You are the guild owner, hence can't ban yourself",
|
||||
403,
|
||||
);
|
||||
throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
|
||||
|
||||
const ban = Ban.create({
|
||||
user_id: req.params.user_id,
|
||||
@ -227,7 +218,7 @@ router.put(
|
||||
]);
|
||||
|
||||
return res.json(ban);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -273,7 +264,7 @@ router.delete(
|
||||
]);
|
||||
|
||||
return res.status(204).send();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -42,7 +42,7 @@ router.get(
|
||||
const channels = await Channel.find({ where: { guild_id } });
|
||||
|
||||
res.json(channels);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -67,13 +67,10 @@ router.post(
|
||||
const { guild_id } = req.params;
|
||||
const body = req.body as ChannelModifySchema;
|
||||
|
||||
const channel = await Channel.createChannel(
|
||||
{ ...body, guild_id },
|
||||
req.user_id,
|
||||
);
|
||||
const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
|
||||
|
||||
res.status(201).json(channel);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -102,9 +99,7 @@ router.patch(
|
||||
});
|
||||
|
||||
// The channels not listed for this query
|
||||
const notMentioned = guild.channel_ordering.filter(
|
||||
(x) => !body.find((c) => c.id == x),
|
||||
);
|
||||
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);
|
||||
@ -124,7 +119,7 @@ router.patch(
|
||||
channel_id: channel.id,
|
||||
guild_id,
|
||||
} as ChannelUpdateEvent);
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// have to do the parents after the positions
|
||||
@ -141,10 +136,7 @@ router.patch(
|
||||
]);
|
||||
|
||||
if (opt.lock_permissions)
|
||||
await Channel.update(
|
||||
{ id: channel.id },
|
||||
{ permission_overwrites: parent.permission_overwrites },
|
||||
);
|
||||
await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
|
||||
|
||||
const parentPos = notMentioned.indexOf(parent.id);
|
||||
notMentioned.splice(parentPos + 1, 0, channel.id);
|
||||
@ -156,16 +148,13 @@ router.patch(
|
||||
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);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -45,8 +45,7 @@ router.post(
|
||||
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);
|
||||
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
|
||||
@ -60,7 +59,7 @@ router.post(
|
||||
]);
|
||||
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -59,7 +59,7 @@ router.get(
|
||||
},
|
||||
minimum_size: 0,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,7 +57,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(emojis);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
@ -86,7 +86,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(emoji);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -116,10 +116,7 @@ router.post(
|
||||
});
|
||||
const { maxEmojis } = Config.get().limits.guild;
|
||||
|
||||
if (emoji_count >= maxEmojis)
|
||||
throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(
|
||||
maxEmojis,
|
||||
);
|
||||
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 } });
|
||||
@ -147,7 +144,7 @@ router.post(
|
||||
} as GuildEmojisUpdateEvent);
|
||||
|
||||
return res.status(201).json(emoji);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -184,7 +181,7 @@ router.patch(
|
||||
} as GuildEmojisUpdateEvent);
|
||||
|
||||
return res.json(emoji);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -216,7 +213,7 @@ router.delete(
|
||||
} as GuildEmojisUpdateEvent);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -58,17 +58,13 @@ router.get(
|
||||
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,
|
||||
);
|
||||
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,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -99,9 +95,7 @@ router.patch(
|
||||
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",
|
||||
);
|
||||
throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
|
||||
|
||||
const guild = await Guild.findOneOrFail({
|
||||
where: { id: guild_id },
|
||||
@ -110,47 +104,29 @@ router.patch(
|
||||
|
||||
// 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.splash && body.splash !== guild.splash)
|
||||
body.splash = await handleFile(
|
||||
`/splashes/${guild_id}`,
|
||||
body.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)),
|
||||
);
|
||||
.concat(body.features.filter((x) => !guild.features.includes(x)));
|
||||
|
||||
// TODO move these
|
||||
const MUTABLE_FEATURES = [
|
||||
"COMMUNITY",
|
||||
"INVITES_DISABLED",
|
||||
"DISCOVERABLE",
|
||||
];
|
||||
const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
|
||||
|
||||
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.
|
||||
@ -179,7 +155,7 @@ router.patch(
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
{ skipPermissionCheck: true },
|
||||
{ skipPermissionCheck: true }
|
||||
);
|
||||
|
||||
await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
|
||||
@ -212,7 +188,7 @@ router.patch(
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
{ skipPermissionCheck: true },
|
||||
{ skipPermissionCheck: true }
|
||||
);
|
||||
|
||||
await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
|
||||
@ -242,7 +218,7 @@ router.patch(
|
||||
]);
|
||||
|
||||
return res.json(data);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -41,7 +41,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(invites);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -36,7 +36,7 @@ router.get(
|
||||
message: "Unknown Guild Member Verification Form",
|
||||
code: 10068,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -62,13 +62,9 @@ router.get(
|
||||
select: {
|
||||
index: true,
|
||||
// only grab public member props
|
||||
...Object.fromEntries(
|
||||
PublicMemberProjection.map((x) => [x, true]),
|
||||
),
|
||||
...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])),
|
||||
// and public user props
|
||||
user: Object.fromEntries(
|
||||
PublicUserProjection.map((x) => [x, true]),
|
||||
),
|
||||
user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
|
||||
roles: {
|
||||
id: true,
|
||||
},
|
||||
@ -80,7 +76,7 @@ router.get(
|
||||
user: member.user.toPublicUser(),
|
||||
roles: member.roles.map((x) => x.id),
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -104,8 +100,7 @@ router.patch(
|
||||
}),
|
||||
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 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({
|
||||
@ -128,19 +123,13 @@ router.patch(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
("bio" in body || "avatar" in body) &&
|
||||
req.params.member_id != "@me"
|
||||
) {
|
||||
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,
|
||||
);
|
||||
body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
|
||||
|
||||
member.assign(body);
|
||||
|
||||
@ -152,8 +141,7 @@ router.patch(
|
||||
body.roles = body.roles || [];
|
||||
body.roles.filter((x) => !!x);
|
||||
|
||||
if (body.roles.indexOf(everyone.id) === -1)
|
||||
body.roles.push(everyone.id);
|
||||
if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
|
||||
// foreign key constraint will fail if role doesn't exist
|
||||
member.roles = body.roles.map((x) => Role.create({ id: x }));
|
||||
}
|
||||
@ -170,7 +158,7 @@ router.patch(
|
||||
} as GuildMemberUpdateEvent);
|
||||
|
||||
res.json(member);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.put(
|
||||
@ -222,7 +210,7 @@ router.put(
|
||||
|
||||
await Member.addToGuild(member_id, guild_id);
|
||||
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -249,7 +237,7 @@ router.delete(
|
||||
|
||||
await Member.removeFromGuild(member_id, guild_id);
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -49,7 +49,7 @@ router.patch(
|
||||
|
||||
await Member.changeNickname(member_id, guild_id, req.body.nick);
|
||||
res.status(200).send();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -38,7 +38,7 @@ router.delete(
|
||||
|
||||
await Member.removeRole(member_id, guild_id, role_id);
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.put(
|
||||
@ -55,7 +55,7 @@ router.put(
|
||||
|
||||
await Member.addRole(member_id, guild_id, role_id);
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -33,8 +33,7 @@ router.get(
|
||||
query: {
|
||||
limit: {
|
||||
type: "number",
|
||||
description:
|
||||
"max number of members to return (1-1000). default 1",
|
||||
description: "max number of members to return (1-1000). default 1",
|
||||
},
|
||||
after: {
|
||||
type: "string",
|
||||
@ -52,8 +51,7 @@ router.get(
|
||||
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");
|
||||
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) } : {};
|
||||
|
||||
@ -67,7 +65,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(members);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -54,14 +54,10 @@ router.get(
|
||||
} = req.query;
|
||||
|
||||
const parsedLimit = Number(limit) || 50;
|
||||
if (parsedLimit < 1 || parsedLimit > 100)
|
||||
throw new HTTPError("limit must be between 1 and 100", 422);
|
||||
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
|
||||
)
|
||||
if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1)
|
||||
throw FieldErrors({
|
||||
sort_order: {
|
||||
message: "Value must be one of ('desc', 'asc').",
|
||||
@ -70,20 +66,13 @@ router.get(
|
||||
}); // todo this is wrong
|
||||
}
|
||||
|
||||
const permissions = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
channel_id as string | undefined,
|
||||
);
|
||||
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 });
|
||||
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
|
||||
|
||||
const query: FindManyOptions<Message> = {
|
||||
order: {
|
||||
timestamp: sort_order
|
||||
? (sort_order.toUpperCase() as "ASC" | "DESC")
|
||||
: "DESC",
|
||||
timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
|
||||
},
|
||||
take: parsedLimit || 0,
|
||||
where: {
|
||||
@ -114,16 +103,8 @@ router.get(
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -170,7 +151,7 @@ router.get(
|
||||
messages: messagesDto,
|
||||
total_results: messages.length,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,10 +57,7 @@ router.patch(
|
||||
});
|
||||
|
||||
if (body.banner)
|
||||
body.banner = await handleFile(
|
||||
`/guilds/${guild_id}/users/${req.user_id}/avatars`,
|
||||
body.banner as string,
|
||||
);
|
||||
body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
|
||||
|
||||
member = await OrmUtils.mergeDeep(member, body);
|
||||
|
||||
@ -74,7 +71,7 @@ router.patch(
|
||||
} as GuildMemberUpdateEvent);
|
||||
|
||||
res.json(member);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -23,12 +23,7 @@ import { IsNull, LessThan } from "typeorm";
|
||||
const router = Router();
|
||||
|
||||
//Returns all inactive members, respecting role hierarchy
|
||||
const inactiveMembers = async (
|
||||
guild_id: string,
|
||||
user_id: string,
|
||||
days: number,
|
||||
roles: string[] = [],
|
||||
) => {
|
||||
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?
|
||||
@ -55,9 +50,7 @@ const inactiveMembers = async (
|
||||
|
||||
//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)),
|
||||
);
|
||||
members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
|
||||
|
||||
const me = await Member.findOneOrFail({
|
||||
where: { id: user_id, guild_id },
|
||||
@ -73,8 +66,8 @@ const inactiveMembers = async (
|
||||
member.roles?.some(
|
||||
(role) =>
|
||||
role.position < myHighestRole || //roles higher than me can't be kicked
|
||||
me.id === guild.owner_id, //owner can kick anyone
|
||||
),
|
||||
me.id === guild.owner_id //owner can kick anyone
|
||||
)
|
||||
);
|
||||
|
||||
return members;
|
||||
@ -95,15 +88,10 @@ router.get(
|
||||
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 });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -127,19 +115,12 @@ router.post(
|
||||
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 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 });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -38,13 +38,8 @@ router.get(
|
||||
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(
|
||||
getIpAdress(req),
|
||||
guild.features.includes("VIP_REGIONS"),
|
||||
),
|
||||
);
|
||||
},
|
||||
return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS")));
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -53,7 +53,7 @@ router.get(
|
||||
where: { guild_id, id: role_id },
|
||||
});
|
||||
return res.json(role);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -75,8 +75,7 @@ router.delete(
|
||||
}),
|
||||
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");
|
||||
if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
|
||||
|
||||
await Promise.all([
|
||||
Role.delete({
|
||||
@ -94,7 +93,7 @@ router.delete(
|
||||
]);
|
||||
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: check role hierarchy
|
||||
@ -123,11 +122,7 @@ router.patch(
|
||||
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,
|
||||
);
|
||||
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({
|
||||
@ -135,10 +130,7 @@ router.patch(
|
||||
});
|
||||
role.assign({
|
||||
...body,
|
||||
permissions: String(
|
||||
(req.permission?.bitfield || 0n) &
|
||||
BigInt(body.permissions || "0"),
|
||||
),
|
||||
permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
@ -154,7 +146,7 @@ router.patch(
|
||||
]);
|
||||
|
||||
res.json(role);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -22,10 +22,7 @@ import { route } from "@spacebar/api";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.patch(
|
||||
"/",
|
||||
route({ permission: "MANAGE_ROLES" }),
|
||||
async (req: Request, res: Response) => {
|
||||
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;
|
||||
@ -40,23 +37,16 @@ router.patch(
|
||||
|
||||
const [add, remove] = partition(
|
||||
members,
|
||||
(member) =>
|
||||
member_ids.includes(member.id) &&
|
||||
!member.roles.map((role) => role.id).includes(role_id),
|
||||
(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),
|
||||
),
|
||||
...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);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -68,8 +68,7 @@ router.post(
|
||||
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 overriden
|
||||
@ -80,10 +79,7 @@ router.post(
|
||||
...body,
|
||||
guild_id: guild_id,
|
||||
managed: false,
|
||||
permissions: String(
|
||||
(req.permission?.bitfield || 0n) &
|
||||
BigInt(body.permissions || "0"),
|
||||
),
|
||||
permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
|
||||
tags: undefined,
|
||||
icon: undefined,
|
||||
unicode_emoji: undefined,
|
||||
@ -112,7 +108,7 @@ router.post(
|
||||
]);
|
||||
|
||||
res.json(role);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -136,11 +132,7 @@ router.patch(
|
||||
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 })),
|
||||
@ -155,12 +147,12 @@ router.patch(
|
||||
guild_id,
|
||||
role: x,
|
||||
},
|
||||
} as GuildRoleUpdateEvent),
|
||||
),
|
||||
} as GuildRoleUpdateEvent)
|
||||
)
|
||||
);
|
||||
|
||||
res.json(roles);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -50,7 +50,7 @@ router.get(
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
|
||||
res.json(await Sticker.find({ where: { guild_id } }));
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const bodyParser = multer({
|
||||
@ -102,7 +102,7 @@ router.post(
|
||||
await sendStickerUpdateEvent(guild_id);
|
||||
|
||||
res.json(sticker);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
function getStickerFormat(mime_type: string) {
|
||||
@ -116,9 +116,7 @@ function getStickerFormat(mime_type: string) {
|
||||
case "image/gif":
|
||||
return StickerFormatType.GIF;
|
||||
default:
|
||||
throw new HTTPError(
|
||||
"invalid sticker format: must be png, apng or lottie",
|
||||
);
|
||||
throw new HTTPError("invalid sticker format: must be png, apng or lottie");
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,9 +139,9 @@ router.get(
|
||||
res.json(
|
||||
await Sticker.findOneOrFail({
|
||||
where: { guild_id, id: sticker_id },
|
||||
}),
|
||||
})
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -175,7 +173,7 @@ router.patch(
|
||||
await sendStickerUpdateEvent(guild_id);
|
||||
|
||||
return res.json(sticker);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
async function sendStickerUpdateEvent(guild_id: string) {
|
||||
@ -207,7 +205,7 @@ router.delete(
|
||||
await sendStickerUpdateEvent(guild_id);
|
||||
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,7 +57,7 @@ router.get(
|
||||
});
|
||||
|
||||
return res.json(templates);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -102,7 +102,7 @@ router.post(
|
||||
}).save();
|
||||
|
||||
res.json(template);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
@ -123,7 +123,7 @@ router.delete(
|
||||
});
|
||||
|
||||
res.json(template);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.put(
|
||||
@ -148,7 +148,7 @@ router.put(
|
||||
}).save();
|
||||
|
||||
res.json(template);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -173,7 +173,7 @@ router.patch(
|
||||
}).save();
|
||||
|
||||
res.json(template);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,13 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Channel,
|
||||
ChannelType,
|
||||
Guild,
|
||||
Invite,
|
||||
VanityUrlSchema,
|
||||
} from "@spacebar/util";
|
||||
import { Channel, ChannelType, Guild, Invite, VanityUrlSchema } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
||||
@ -64,11 +58,9 @@ router.get(
|
||||
});
|
||||
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(
|
||||
@ -94,11 +86,9 @@ router.patch(
|
||||
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");
|
||||
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");
|
||||
@ -112,7 +102,7 @@ router.patch(
|
||||
{ guild_id },
|
||||
{
|
||||
code: code,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({ code });
|
||||
@ -132,7 +122,7 @@ router.patch(
|
||||
}).save();
|
||||
|
||||
return res.json({ code: code });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -52,14 +52,9 @@ router.patch(
|
||||
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 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
|
||||
@ -98,7 +93,7 @@ router.patch(
|
||||
} as VoiceStateUpdateEvent),
|
||||
]);
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,12 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Channel,
|
||||
Guild,
|
||||
GuildUpdateWelcomeScreenSchema,
|
||||
Member,
|
||||
} from "@spacebar/util";
|
||||
import { Channel, Guild, GuildUpdateWelcomeScreenSchema, Member } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router: Router = Router();
|
||||
@ -46,7 +41,7 @@ router.get(
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
|
||||
res.json(guild.welcome_screen);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
@ -70,11 +65,9 @@ router.patch(
|
||||
|
||||
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
|
||||
@ -83,8 +76,8 @@ router.patch(
|
||||
Channel.findOneOrFail({
|
||||
where: { id: channel_id, guild_id },
|
||||
select: { id: true },
|
||||
}),
|
||||
) || [],
|
||||
})
|
||||
) || []
|
||||
);
|
||||
guild.welcome_screen.welcome_channels = body.welcome_channels;
|
||||
}
|
||||
@ -92,7 +85,7 @@ router.patch(
|
||||
await guild.save();
|
||||
|
||||
res.status(200).json(guild.welcome_screen);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -81,10 +81,8 @@ router.get(
|
||||
// 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
|
||||
Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) ===
|
||||
Permissions.FLAGS.CONNECT
|
||||
) {
|
||||
channels.push({
|
||||
id: doc.id,
|
||||
@ -110,7 +108,7 @@ router.get(
|
||||
|
||||
res.set("Cache-Control", "public, max-age=300");
|
||||
return res.json(data);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -57,15 +57,8 @@ router.get(
|
||||
|
||||
// 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,
|
||||
);
|
||||
if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
|
||||
throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
|
||||
}
|
||||
|
||||
// Setup canvas
|
||||
@ -74,17 +67,7 @@ router.get(
|
||||
const sizeOf = require("image-size");
|
||||
|
||||
// TODO: Widget style templates need Spacebar branding
|
||||
const source = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"assets",
|
||||
"widget",
|
||||
`${style}.png`,
|
||||
);
|
||||
const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`);
|
||||
if (!fs.existsSync(source)) {
|
||||
throw new HTTPError("Widget template does not exist.", 400);
|
||||
}
|
||||
@ -100,99 +83,32 @@ router.get(
|
||||
switch (style) {
|
||||
case "shield":
|
||||
ctx.textAlign = "center";
|
||||
await drawText(
|
||||
ctx,
|
||||
73,
|
||||
13,
|
||||
"#FFFFFF",
|
||||
"thin 10px Verdana",
|
||||
presence,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
@ -201,16 +117,10 @@ router.get(
|
||||
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,
|
||||
) {
|
||||
async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) {
|
||||
const img = new (require("canvas").Image)();
|
||||
img.src = icon;
|
||||
|
||||
@ -234,12 +144,11 @@ async function drawText(
|
||||
color: string,
|
||||
font: string,
|
||||
text: string,
|
||||
maxcharacters?: number,
|
||||
maxcharacters?: number
|
||||
) {
|
||||
canvas.fillStyle = color;
|
||||
canvas.font = font;
|
||||
if (text.length > (maxcharacters || 0) && maxcharacters)
|
||||
text = text.slice(0, maxcharacters) + "...";
|
||||
if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
|
||||
canvas.fillText(text, x, y);
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ router.get(
|
||||
enabled: guild.widget_enabled || false,
|
||||
channel_id: guild.widget_channel_id || null,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// https://discord.com/developers/docs/resources/guild#modify-guild-widget
|
||||
@ -74,12 +74,12 @@ router.patch(
|
||||
{
|
||||
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);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,14 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Config,
|
||||
DiscordApiErrors,
|
||||
Guild,
|
||||
GuildCreateSchema,
|
||||
Member,
|
||||
getRights,
|
||||
} from "@spacebar/util";
|
||||
import { Config, DiscordApiErrors, Guild, GuildCreateSchema, Member, getRights } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router: Router = Router();
|
||||
@ -73,7 +66,7 @@ router.post(
|
||||
await Member.addToGuild(req.user_id, guild.id);
|
||||
|
||||
res.status(201).json(guild);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -47,13 +47,11 @@ router.get(
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { allowDiscordTemplates, allowRaws, enabled } =
|
||||
Config.get().templates;
|
||||
const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
|
||||
if (!enabled)
|
||||
res.json({
|
||||
code: 403,
|
||||
message:
|
||||
"Template creation & usage is disabled on this instance.",
|
||||
message: "Template creation & usage is disabled on this instance.",
|
||||
}).sendStatus(403);
|
||||
|
||||
const { code } = req.params;
|
||||
@ -63,8 +61,7 @@ router.get(
|
||||
return res
|
||||
.json({
|
||||
code: 403,
|
||||
message:
|
||||
"Discord templates cannot be used on this instance.",
|
||||
message: "Discord templates cannot be used on this instance.",
|
||||
})
|
||||
.sendStatus(403);
|
||||
const discordTemplateID = code.split("discord:", 2)[1];
|
||||
@ -74,7 +71,7 @@ router.get(
|
||||
{
|
||||
method: "get",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
}
|
||||
);
|
||||
return res.json(await discordTemplateData.json());
|
||||
}
|
||||
@ -95,13 +92,10 @@ router.get(
|
||||
where: { code: code },
|
||||
});
|
||||
res.json(template);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:code",
|
||||
route({ requestBody: "GuildTemplateCreateSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
router.post("/:code", route({ requestBody: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
|
||||
const {
|
||||
enabled,
|
||||
allowTemplateCreation,
|
||||
@ -112,8 +106,7 @@ router.post(
|
||||
return res
|
||||
.json({
|
||||
code: 403,
|
||||
message:
|
||||
"Template creation & usage is disabled on this instance.",
|
||||
message: "Template creation & usage is disabled on this instance.",
|
||||
})
|
||||
.sendStatus(403);
|
||||
if (!allowTemplateCreation)
|
||||
@ -163,7 +156,6 @@ router.post(
|
||||
await Member.addToGuild(req.user_id, guild_id);
|
||||
|
||||
res.status(201).json({ id: guild.id });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -53,7 +53,7 @@ router.get(
|
||||
});
|
||||
|
||||
res.status(200).send(invite);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -89,21 +89,14 @@ router.post(
|
||||
where: { id: req.user_id },
|
||||
});
|
||||
|
||||
if (
|
||||
features.includes("INTERNAL_EMPLOYEE_ONLY") &&
|
||||
(public_flags & 1) !== 1
|
||||
)
|
||||
throw new HTTPError(
|
||||
"Only intended for the staff of this server.",
|
||||
401,
|
||||
);
|
||||
if (features.includes("INVITES_DISABLED"))
|
||||
throw new HTTPError("Sorry, this guild has joins closed.", 403);
|
||||
if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1)
|
||||
throw new HTTPError("Only intended for the staff of this server.", 401);
|
||||
if (features.includes("INVITES_DISABLED")) throw new HTTPError("Sorry, this guild has joins closed.", 403);
|
||||
|
||||
const invite = await Invite.joinGuild(req.user_id, code);
|
||||
|
||||
res.json(invite);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// * cant use permission of route() function because path doesn't have guild_id/channel_id
|
||||
@ -127,20 +120,10 @@ router.delete(
|
||||
const invite = await Invite.findOneOrFail({ where: { 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 }),
|
||||
@ -156,7 +139,7 @@ router.delete(
|
||||
]);
|
||||
|
||||
res.json({ invite: invite });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,11 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Application,
|
||||
DiscordApiErrors,
|
||||
PublicUserProjection,
|
||||
} from "@spacebar/util";
|
||||
import { Application, DiscordApiErrors, PublicUserProjection } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router: Router = Router();
|
||||
@ -40,9 +36,7 @@ router.get(
|
||||
where: { id: req.params.id },
|
||||
relations: ["bot", "owner"],
|
||||
select: {
|
||||
owner: Object.fromEntries(
|
||||
PublicUserProjection.map((x) => [x, true]),
|
||||
),
|
||||
owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
|
||||
},
|
||||
});
|
||||
|
||||
@ -51,9 +45,8 @@ router.get(
|
||||
res.json({
|
||||
...app,
|
||||
owner: app.owner.toPublicUser(),
|
||||
install_params:
|
||||
app.install_params !== null ? app.install_params : undefined,
|
||||
install_params: app.install_params !== null ? app.install_params : undefined,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
export default router;
|
||||
|
@ -84,13 +84,7 @@ router.get(
|
||||
id: req.user_id,
|
||||
bot: false,
|
||||
},
|
||||
select: [
|
||||
"id",
|
||||
"username",
|
||||
"avatar",
|
||||
"discriminator",
|
||||
"public_flags",
|
||||
],
|
||||
select: ["id", "username", "avatar", "discriminator", "public_flags"],
|
||||
});
|
||||
|
||||
const guilds = await Member.find({
|
||||
@ -165,7 +159,7 @@ router.get(
|
||||
},
|
||||
authorized: false,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
@ -210,17 +204,9 @@ router.post(
|
||||
// TODO: captcha verification
|
||||
// TODO: MFA verification
|
||||
|
||||
const perms = await getPermission(
|
||||
req.user_id,
|
||||
body.guild_id,
|
||||
undefined,
|
||||
{ member_relations: ["user"] },
|
||||
);
|
||||
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
|
||||
)
|
||||
if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot)
|
||||
throw DiscordApiErrors.UNAUTHORIZED;
|
||||
perms.hasThrow("MANAGE_GUILD");
|
||||
|
||||
@ -234,19 +220,14 @@ router.post(
|
||||
// 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,
|
||||
);
|
||||
if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
|
||||
|
||||
await Member.addToGuild(app.id, body.guild_id);
|
||||
|
||||
return res.json({
|
||||
location: "/oauth2/authorized", // redirect URL
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -48,7 +48,7 @@ router.get(
|
||||
tosPage: general.tosPage,
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -34,20 +34,14 @@ router.get(
|
||||
const { cdn, gateway, api } = Config.get();
|
||||
|
||||
const IdentityForm = {
|
||||
cdn:
|
||||
cdn.endpointPublic ||
|
||||
process.env.CDN ||
|
||||
"http://localhost:3001",
|
||||
gateway:
|
||||
gateway.endpointPublic ||
|
||||
process.env.GATEWAY ||
|
||||
"ws://localhost:3001",
|
||||
cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
|
||||
gateway: gateway.endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
|
||||
defaultApiVersion: api.defaultVersion ?? 9,
|
||||
apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/",
|
||||
};
|
||||
|
||||
res.json(IdentityForm);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -33,7 +33,7 @@ router.get(
|
||||
async (req: Request, res: Response) => {
|
||||
const { general } = Config.get();
|
||||
res.json(general);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -33,7 +33,7 @@ router.get(
|
||||
async (req: Request, res: Response) => {
|
||||
const { limits } = Config.get();
|
||||
res.json(limits);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -17,14 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
Config,
|
||||
getRights,
|
||||
Guild,
|
||||
Member,
|
||||
Message,
|
||||
User,
|
||||
} from "@spacebar/util";
|
||||
import { Config, getRights, Guild, Member, Message, User } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router();
|
||||
|
||||
@ -54,7 +47,7 @@ router.get(
|
||||
members: await Member.count(),
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -61,7 +61,7 @@ router.post(
|
||||
]);
|
||||
|
||||
return res.status(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -20,15 +20,11 @@ import { Router, Request, Response } from "express";
|
||||
import { route } from "@spacebar/api";
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
"/scheduled-maintenances/upcoming.json",
|
||||
route({}),
|
||||
async (req: Request, res: Response) => {
|
||||
router.get("/scheduled-maintenances/upcoming.json", route({}), async (req: Request, res: Response) => {
|
||||
res.json({
|
||||
page: {},
|
||||
scheduled_maintenances: {},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -31,7 +31,7 @@ router.post(
|
||||
(req: Request, res: Response) => {
|
||||
// TODO:
|
||||
res.sendStatus(204);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user