1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-24 19:32:46 +01:00

Actually run prettier

This commit is contained in:
Emma [it/its]@Rory& 2023-12-11 01:12:54 +01:00
parent daeb6c3587
commit 0a8ceb9e63
250 changed files with 2905 additions and 6286 deletions

View File

@ -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;

View File

@ -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")
);
}

View File

@ -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 = {};

View File

@ -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

View File

@ -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`
);
}

View File

@ -25,7 +25,7 @@ const doTimedIdentify = () =>
token: TOKEN,
properties: {},
},
}),
})
);
break;
case OPCODES.Dispatch:

View File

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

View File

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

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

View File

@ -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 });

View File

@ -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) {

View File

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

View File

@ -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" });
}
}

View File

@ -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) {

View File

@ -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",
});

View File

@ -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;

View File

@ -34,7 +34,7 @@ router.get(
// TODO:
//const { exclude_consumed } = req.query;
res.status(200).send([]);
},
}
);
export default router;

View File

@ -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;

View File

@ -32,7 +32,7 @@ router.get(
}),
async (req: Request, res: Response) => {
res.json([]).status(200);
},
}
);
export default router;

View File

@ -33,7 +33,7 @@ router.get(
async (req: Request, res: Response) => {
//TODO
res.send([]).status(200);
},
}
);
export default router;

View File

@ -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;

View File

@ -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;

View File

@ -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 });
},
}
);

View File

@ -38,7 +38,7 @@ router.get(
country_code: country_code,
promotional_email_opt_in: { required: true, pre_checked: false },
});
},
}
);
export default router;

View File

@ -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 } });
},
}
);
/**

View File

@ -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();
},
}
);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -52,7 +52,7 @@ router.post(
nonce: "NoncePlaceholder",
regenerate_nonce: "RegenNoncePlaceholder",
});
},
}
);
export default router;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -57,7 +57,7 @@ router.post(
flags: 1,
components: [],
}).status(200);
},
}
);
export default router;

View File

@ -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;

View File

@ -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;

View File

@ -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);
},
}
);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
},
}
);

View File

@ -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;

View File

@ -64,7 +64,7 @@ router.post(
} as TypingStartEvent);
res.sendStatus(204);
},
}
);
export default router;

View File

@ -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;

View File

@ -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(", "),
}),
},
});

View File

@ -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;

View File

@ -70,7 +70,7 @@ router.get(
offset: Number(offset || Config.get().guild.discovery.offset),
limit: Number(limit || configLimit),
});
},
}
);
export default router;

View File

@ -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;

View File

@ -52,7 +52,7 @@ router.get(
});
res.redirect(release.url);
},
}
);
export default router;

View File

@ -43,7 +43,7 @@ router.get(
max_concurrency: 1,
},
});
},
}
);
export default router;

View File

@ -36,7 +36,7 @@ router.get(
res.json({
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
});
},
}
);
export default router;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -59,7 +59,7 @@ router.get(
},
minimum_size: 0,
});
},
}
);
export default router;

View File

@ -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;

View File

@ -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;

View File

@ -41,7 +41,7 @@ router.get(
});
return res.json(invites);
},
}
);
export default router;

View File

@ -36,7 +36,7 @@ router.get(
message: "Unknown Guild Member Verification Form",
code: 10068,
});
},
}
);
export default router;

View File

@ -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;

View File

@ -49,7 +49,7 @@ router.patch(
await Member.changeNickname(member_id, guild_id, req.body.nick);
res.status(200).send();
},
}
);
export default router;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -48,7 +48,7 @@ router.get(
tosPage: general.tosPage,
},
});
},
}
);
export default router;

View File

@ -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;

View File

@ -33,7 +33,7 @@ router.get(
async (req: Request, res: Response) => {
const { general } = Config.get();
res.json(general);
},
}
);
export default router;

View File

@ -33,7 +33,7 @@ router.get(
async (req: Request, res: Response) => {
const { limits } = Config.get();
res.json(limits);
},
}
);
export default router;

View File

@ -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;

View File

@ -61,7 +61,7 @@ router.post(
]);
return res.status(204);
},
}
);
export default router;

View File

@ -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;

View File

@ -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