diff --git a/api/src/middlewares/TestClient.ts b/api/src/middlewares/TestClient.ts
deleted file mode 100644
index ecf87681..00000000
--- a/api/src/middlewares/TestClient.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import express, { Request, Response, Application } from "express";
-import fs from "fs";
-import path from "path";
-import fetch, { Response as FetchResponse } from "node-fetch";
-import ProxyAgent from 'proxy-agent';
-import { Config } from "@fosscord/util";
-
-export default function TestClient(app: Application) {
- const agent = new ProxyAgent();
- const assetCache = new Map<string, { response: FetchResponse; buffer: Buffer }>();
- const indexHTML = fs.readFileSync(path.join(__dirname, "..", "..", "client_test", "index.html"), { encoding: "utf8" });
-
- var html = indexHTML;
- const CDN_ENDPOINT = (Config.get().cdn.endpointClient || Config.get()?.cdn.endpointPublic || process.env.CDN || "").replace(
- /(https?)?(:\/\/?)/g,
- ""
- );
- const GATEWAY_ENDPOINT = Config.get().gateway.endpointClient || Config.get()?.gateway.endpointPublic || process.env.GATEWAY || "";
-
- if (CDN_ENDPOINT) {
- html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${CDN_ENDPOINT}\`,`);
- }
- if (GATEWAY_ENDPOINT) {
- html = html.replace(/GATEWAY_ENDPOINT: .+/, `GATEWAY_ENDPOINT: \`${GATEWAY_ENDPOINT}\`,`);
- }
- // inline plugins
- var files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "preload-plugins"));
- var plugins = "";
- files.forEach(x =>{if(x.endsWith(".js")) plugins += `<script>${fs.readFileSync(path.join(__dirname, "..", "..", "assets", "preload-plugins", x))}</script>\n`; });
- html = html.replaceAll("<!-- preload plugin marker -->", plugins);
-
- // plugins
- files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "plugins"));
- plugins = "";
- files.forEach(x =>{if(x.endsWith(".js")) plugins += `<script src='/assets/plugins/${x}'></script>\n`; });
- html = html.replaceAll("<!-- plugin marker -->", plugins);
- //preload plugins
- files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "preload-plugins"));
- plugins = "";
- files.forEach(x =>{if(x.endsWith(".js")) plugins += `<script>${fs.readFileSync(path.join(__dirname, "..", "..", "assets", "preload-plugins", x))}</script>\n`; });
- html = html.replaceAll("<!-- preload plugin marker -->", plugins);
-
-
- app.use("/assets", express.static(path.join(__dirname, "..", "..", "assets")));
-
- app.get("/assets/:file", async (req: Request, res: Response) => {
- delete req.headers.host;
- var response: FetchResponse;
- var buffer: Buffer;
- const cache = assetCache.get(req.params.file);
- if (!cache) {
- response = await fetch(`https://discord.com/assets/${req.params.file}`, {
- agent,
- // @ts-ignore
- headers: {
- ...req.headers
- }
- });
- buffer = await response.buffer();
- } else {
- response = cache.response;
- buffer = cache.buffer;
- }
-
- response.headers.forEach((value, name) => {
- if (
- [
- "content-length",
- "content-security-policy",
- "strict-transport-security",
- "set-cookie",
- "transfer-encoding",
- "expect-ct",
- "access-control-allow-origin",
- "content-encoding"
- ].includes(name.toLowerCase())
- ) {
- return;
- }
- res.set(name, value);
- });
- assetCache.set(req.params.file, { buffer, response });
-
- return res.send(buffer);
- });
- app.get("/developers*", (req: Request, res: Response) => {
- const { useTestClient } = Config.get().client;
- res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24);
- res.set("content-type", "text/html");
-
- if(!useTestClient) return res.send("Test client is disabled on this instance. Use a stand-alone client to connect this instance.")
-
- res.send(fs.readFileSync(path.join(__dirname, "..", "..", "client_test", "developers.html"), { encoding: "utf8" }));
- });
- app.get("*", (req: Request, res: Response) => {
- const { useTestClient } = Config.get().client;
- res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24);
- res.set("content-type", "text/html");
-
- if(req.url.startsWith("/api") || req.url.startsWith("/__development")) return;
-
- if(!useTestClient) return res.send("Test client is disabled on this instance. Use a stand-alone client to connect this instance.")
- if (req.url.startsWith("/invite")) return res.send(html.replace("9b2b7f0632acd0c5e781", "9f24f709a3de09b67c49"));
-
- res.send(html);
- });
-}
\ No newline at end of file
diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts
deleted file mode 100644
index 0aa2baa9..00000000
--- a/api/src/routes/discoverable-guilds.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Guild, Config } from "@fosscord/util";
-
-import { Router, Request, Response } from "express";
-import { route } from "@fosscord/api";
-
-const router = Router();
-
-router.get("/", route({}), async (req: Request, res: Response) => {
- const { offset, limit, categories } = req.query;
- var showAllGuilds = Config.get().guild.discovery.showAllGuilds;
- var configLimit = Config.get().guild.discovery.limit;
- // ! this only works using SQL querys
- // TODO: implement this with default typeorm query
- // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) });
- let guilds;
- if (categories == undefined) {
- guilds = showAllGuilds
- ? await Guild.find({ take: Math.abs(Number(limit || configLimit)) })
- : await Guild.find({ where: `"features" LIKE '%DISCOVERABLE%'`, take: Math.abs(Number(limit || configLimit)) });
- } else {
- guilds = showAllGuilds
- ? await Guild.find({ where: `"primary_category_id" = ${categories}`, take: Math.abs(Number(limit || configLimit)) })
- : await Guild.find({
- where: `"primary_category_id" = ${categories} AND "features" LIKE '%DISCOVERABLE%'`,
- take: Math.abs(Number(limit || configLimit))
- });
- }
-
- const total = guilds ? guilds.length : undefined;
-
- res.send({ total: total, guilds: guilds, offset: Number(offset || Config.get().guild.discovery.offset), limit: Number(limit || configLimit) });
-});
-
-export default router;
diff --git a/api/src/routes/ping.ts b/api/src/routes/ping.ts
deleted file mode 100644
index 5cdea705..00000000
--- a/api/src/routes/ping.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Router, Response, Request } from "express";
-import { route } from "@fosscord/api";
-
-const router = Router();
-
-router.get("/", route({}), (req: Request, res: Response) => {
- res.send("pong");
-});
-
-export default router;
diff --git a/api/src/routes/users/@me/notes.ts b/api/src/routes/users/@me/notes.ts
deleted file mode 100644
index 4887b191..00000000
--- a/api/src/routes/users/@me/notes.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Request, Response, Router } from "express";
-import { route } from "@fosscord/api";
-import { User, emitEvent } from "@fosscord/util";
-
-const router: Router = Router();
-
-router.get("/:id", route({}), async (req: Request, res: Response) => {
- const { id } = req.params;
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["notes"] });
-
- const note = user.notes[id];
- return res.json({
- note: note,
- note_user_id: id,
- user_id: user.id,
- });
-});
-
-router.put("/:id", route({}), async (req: Request, res: Response) => {
- const { id } = req.params;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const noteUser = await User.findOneOrFail({ where: { id: id }}); //if noted user does not exist throw
- const { note } = req.body;
-
- await User.update({ id: req.user_id }, { notes: { ...user.notes, [noteUser.id]: note } });
-
- await emitEvent({
- event: "USER_NOTE_UPDATE",
- data: {
- note: note,
- id: noteUser.id
- },
- user_id: user.id,
- })
-
- return res.status(204);
-});
-
-export default router;
diff --git a/api/src/Server.ts b/src/api/Server.ts
index 4cf0917d..136f9814 100644
--- a/api/src/Server.ts
+++ b/src/api/Server.ts
@@ -1,7 +1,6 @@
-import "missing-native-js-functions";
import { Server, ServerOptions } from "lambert-server";
import { Authentication, CORS } from "./middlewares/";
-import { Config, initDatabase, initEvent } from "@fosscord/util";
+import { Config, getOrInitialiseDatabase, initEvent, registerRoutes } from "@fosscord/util";
import { ErrorHandler } from "./middlewares/ErrorHandler";
import { BodyParser } from "./middlewares/BodyParser";
import { Router, Request, Response, NextFunction } from "express";
@@ -11,7 +10,6 @@ import TestClient from "./middlewares/TestClient";
import { initTranslation } from "./middlewares/Translation";
import morgan from "morgan";
import { initInstance } from "./util/handlers/Instance";
-import { registerRoutes } from "@fosscord/util";
import { red } from "picocolors"
export interface FosscordServerOptions extends ServerOptions {}
@@ -34,7 +32,7 @@ export class FosscordServer extends Server {
}
async start() {
- await initDatabase();
+ await getOrInitialiseDatabase();
await Config.init();
await initEvent();
await initInstance();
@@ -44,13 +42,13 @@ export class FosscordServer extends Server {
this.app.use(
morgan("combined", {
skip: (req, res) => {
- var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
+ let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
return skip;
}
})
);
- };
+ }
this.app.use(CORS);
this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
@@ -91,4 +89,4 @@ export class FosscordServer extends Server {
return super.start();
}
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/api/src/global.d.ts b/src/api/global.d.ts
index 7751af8f..7751af8f 100644
--- a/api/src/global.d.ts
+++ b/src/api/global.d.ts
diff --git a/api/src/index.ts b/src/api/index.ts
index adc7649c..adc7649c 100644
--- a/api/src/index.ts
+++ b/src/api/index.ts
diff --git a/api/src/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 5a08caf3..2d9ccf57 100644
--- a/api/src/middlewares/Authentication.ts
+++ b/src/api/middlewares/Authentication.ts
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { checkToken, Config, Rights } from "@fosscord/util";
export const NO_AUTHORIZATION_ROUTES = [
@@ -7,6 +7,7 @@ export const NO_AUTHORIZATION_ROUTES = [
"/auth/login",
"/auth/register",
"/auth/location-metadata",
+ "/auth/mfa/totp",
// Routes with a seperate auth system
"/webhooks/",
// Public information endpoints
diff --git a/api/src/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index 4cb376bc..35db3c6f 100644
--- a/api/src/middlewares/BodyParser.ts
+++ b/src/api/middlewares/BodyParser.ts
@@ -1,6 +1,6 @@
import bodyParser, { OptionsJson } from "body-parser";
import { NextFunction, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
export function BodyParser(opts?: OptionsJson) {
const jsonParser = bodyParser.json(opts);
diff --git a/api/src/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 20260cf9..20260cf9 100644
--- a/api/src/middlewares/CORS.ts
+++ b/src/api/middlewares/CORS.ts
diff --git a/api/src/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index 2012b91c..8a046e06 100644
--- a/api/src/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { ApiError, FieldError } from "@fosscord/util";
const EntityNotFoundErrorRegex = /"(\w+)"/;
diff --git a/api/src/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 1a38cfcf..47180b62 100644
--- a/api/src/middlewares/RateLimit.ts
+++ b/src/api/middlewares/RateLimit.ts
@@ -1,4 +1,4 @@
-import { Config, listenEvent } from "@fosscord/util";
+import { Config, getRights, listenEvent, Rights } from "@fosscord/util";
import { NextFunction, Request, Response, Router } from "express";
import { getIpAdress } from "@fosscord/api";
import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
@@ -9,6 +9,7 @@ import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
/*
? bucket limit? Max actions/sec per bucket?
+(ANSWER: a small fosscord instance might not need a complex rate limiting system)
TODO: delay database requests to include multiple queries
TODO: different for methods (GET/POST)
@@ -27,7 +28,7 @@ type RateLimit = {
expires_at: Date;
};
-var Cache = new Map<string, RateLimit>();
+let Cache = new Map<string, RateLimit>();
const EventRateLimit = "RATELIMIT";
export default function rateLimit(opts: {
@@ -44,21 +45,27 @@ export default function rateLimit(opts: {
onlyIp?: boolean;
}): any {
return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
+ // exempt user? if so, immediately short circuit
+ if (req.user_id) {
+ const rights = await getRights(req.user_id);
+ if (rights.has("BYPASS_RATE_LIMITS")) return;
+ }
+
const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
- var executor_id = getIpAdress(req);
+ let executor_id = getIpAdress(req);
if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
- var max_hits = opts.count;
+ let max_hits = opts.count;
if (opts.bot && req.user_bot) max_hits = opts.bot;
if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
- const offender = Cache.get(executor_id + bucket_id);
+ let offender = Cache.get(executor_id + bucket_id);
if (offender) {
- const reset = offender.expires_at.getTime();
- const resetAfterMs = reset - Date.now();
- const resetAfterSec = resetAfterMs / 1000;
+ let reset = offender.expires_at.getTime();
+ let resetAfterMs = reset - Date.now();
+ let resetAfterSec = Math.ceil(resetAfterMs / 1000);
if (resetAfterMs <= 0) {
offender.hits = 0;
@@ -70,6 +77,11 @@ export default function rateLimit(opts: {
if (offender.blocked) {
const global = bucket_id === "global";
+ // each block violation pushes the expiry one full window further
+ reset += opts.window * 1000;
+ offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
+ resetAfterMs = reset - Date.now();
+ resetAfterSec = Math.ceil(resetAfterMs / 1000);
console.log("blocked bucket: " + bucket_id, { resetAfterMs });
return (
@@ -151,9 +163,9 @@ export async function initRateLimits(app: Router) {
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;
- var limit = Cache.get(id);
+ let limit = Cache.get(id);
if (!limit) {
limit = {
id: opts.bucket_id,
@@ -171,7 +183,7 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
}
/*
- var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id });
+ let ratelimit = await RateLimit.findOne({ where: { id: opts.bucket_id, executor_id: opts.executor_id } });
if (!ratelimit) {
ratelimit = new RateLimit({
id: opts.bucket_id,
diff --git a/api/src/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index baabf221..64b03bf8 100644
--- a/api/src/middlewares/Translation.ts
+++ b/src/api/middlewares/Translation.ts
@@ -6,8 +6,8 @@ import i18nextBackend from "i18next-node-fs-backend";
import { Router } from "express";
export async function initTranslation(router: Router) {
- const languages = fs.readdirSync(path.join(__dirname, "..", "..", "locales"));
- const namespaces = fs.readdirSync(path.join(__dirname, "..", "..", "locales", "en"));
+ const languages = fs.readdirSync(path.join(__dirname, "..", "..", "..", "assets", "locales"));
+ const namespaces = fs.readdirSync(path.join(__dirname, "..", "..", "..", "assets", "locales", "en"));
const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
await i18next
@@ -19,7 +19,7 @@ export async function initTranslation(router: Router) {
fallbackLng: "en",
ns,
backend: {
- loadPath: __dirname + "/../../locales/{{lng}}/{{ns}}.json"
+ loadPath: __dirname + "/../../../assets/locales/{{lng}}/{{ns}}.json"
},
load: "all"
});
diff --git a/api/src/middlewares/index.ts b/src/api/middlewares/index.ts
index f0c50dbe..f0c50dbe 100644
--- a/api/src/middlewares/index.ts
+++ b/src/api/middlewares/index.ts
diff --git a/api/src/routes/-/healthz.ts b/src/api/routes/-/healthz.ts
index f7bcfebf..f7bcfebf 100644
--- a/api/src/routes/-/healthz.ts
+++ b/src/api/routes/-/healthz.ts
diff --git a/api/src/routes/-/readyz.ts b/src/api/routes/-/readyz.ts
index f7bcfebf..f7bcfebf 100644
--- a/api/src/routes/-/readyz.ts
+++ b/src/api/routes/-/readyz.ts
diff --git a/api/src/routes/applications/#id/entitlements.ts b/src/api/routes/applications/#id/entitlements.ts
index cfcfe40f..cfcfe40f 100644
--- a/api/src/routes/applications/#id/entitlements.ts
+++ b/src/api/routes/applications/#id/entitlements.ts
diff --git a/api/src/routes/applications/index.ts b/src/api/routes/applications/#id/skus.ts
index 28ce42da..5b667f36 100644
--- a/api/src/routes/applications/index.ts
+++ b/src/api/routes/applications/#id/skus.ts
@@ -1,11 +1,11 @@
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
+import { Application, OrmUtils, Team, trimSpecial, User } from "@fosscord/util";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.send([]).status(200);
+ res.json([]).status(200);
});
-export default router;
+export default router;
\ No newline at end of file
diff --git a/api/src/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts
index 28ce42da..28ce42da 100644
--- a/api/src/routes/applications/detectable.ts
+++ b/src/api/routes/applications/detectable.ts
diff --git a/api/src/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index f4c2bd16..f4c2bd16 100644
--- a/api/src/routes/auth/location-metadata.ts
+++ b/src/api/routes/auth/location-metadata.ts
diff --git a/api/src/routes/auth/login.ts b/src/api/routes/auth/login.ts
index a89721ea..9fc5924d 100644
--- a/api/src/routes/auth/login.ts
+++ b/src/api/routes/auth/login.ts
@@ -1,24 +1,15 @@
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
-import { Config, User, generateToken, adjustEmail, FieldErrors } from "@fosscord/util";
+import { Config, User, generateToken, adjustEmail, FieldErrors, LoginSchema } from "@fosscord/util";
+import crypto from "crypto";
const router: Router = Router();
export default router;
-export interface LoginSchema {
- login: string;
- password: string;
- undelete?: boolean;
- captcha_key?: string;
- login_source?: string;
- gift_code_sku_id?: string;
-}
-
router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Response) => {
const { login, password, captcha_key, undelete } = req.body as LoginSchema;
const email = adjustEmail(login);
- console.log("login", email);
const config = Config.get();
@@ -37,7 +28,7 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo
const user = await User.findOneOrFail({
where: [{ phone: login }, { email: login }],
- select: ["data", "id", "disabled", "deleted", "settings"]
+ select: ["data", "id", "disabled", "deleted", "settings", "totp_secret", "mfa_enabled"]
}).catch((e) => {
throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } });
});
@@ -57,6 +48,20 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo
throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
}
+ if (user.mfa_enabled) {
+ // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
+ const ticket = crypto.randomBytes(40).toString("hex");
+
+ await User.update({ id: user.id }, { totp_last_ticket: ticket });
+
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
+ })
+ }
+
const token = await generateToken(user.id);
// Notice this will have a different token structure, than discord
diff --git a/api/src/routes/auth/register.ts b/src/api/routes/auth/register.ts
index cd1bcb72..09366a12 100644
--- a/api/src/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -1,38 +1,11 @@
import { Request, Response, Router } from "express";
-import { Config, generateToken, Invite, FieldErrors, User, adjustEmail, trimSpecial } from "@fosscord/util";
+import { Config, generateToken, Invite, FieldErrors, User, adjustEmail, trimSpecial, RegisterSchema } from "@fosscord/util";
import { route, getIpAdress, IPAnalysis, isProxy } from "@fosscord/api";
-import "missing-native-js-functions";
import bcrypt from "bcrypt";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
const router: Router = Router();
-export interface RegisterSchema {
- /**
- * @minLength 2
- * @maxLength 32
- */
- username: string;
- /**
- * @minLength 1
- * @maxLength 72
- */
- password?: string;
- consent: boolean;
- /**
- * @TJS-format email
- */
- email?: string;
- fingerprint?: string;
- invite?: string;
- /**
- * @TJS-type string
- */
- date_of_birth?: Date; // "2000-04-03"
- gift_code_sku_id?: string;
- captcha_key?: string;
-}
-
router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => {
const body = req.body as RegisterSchema;
const { register, security } = Config.get();
@@ -108,7 +81,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
}
// check if there is already an account with this email
- const exists = await User.findOne({ email: email });
+ const exists = await User.findOne({ where: { email: email } });
if (exists) {
throw FieldErrors({
@@ -128,7 +101,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
throw FieldErrors({
date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
});
- } else if (register.dateOfBirth.minimum) {
+ } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
const minimum = new Date();
minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
body.date_of_birth = new Date(body.date_of_birth as Date);
@@ -167,8 +140,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
await Invite.joinGuild(user.id, body.invite);
}
- console.log("register", body.email, body.username, ip);
-
return res.json({ token: await generateToken(user.id) });
});
diff --git a/api/src/routes/channels/#channel_id/followers.ts b/src/api/routes/channels/#channel_id/followers.ts
index 641af4f8..641af4f8 100644
--- a/api/src/routes/channels/#channel_id/followers.ts
+++ b/src/api/routes/channels/#channel_id/followers.ts
diff --git a/api/src/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 2fca4fdf..bb8b868b 100644
--- a/api/src/routes/channels/#channel_id/index.ts
+++ b/src/api/routes/channels/#channel_id/index.ts
@@ -6,10 +6,12 @@ import {
ChannelUpdateEvent,
emitEvent,
Recipient,
- handleFile
+ handleFile,
+ ChannelModifySchema
} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
// TODO: delete channel
@@ -18,7 +20,7 @@ const router: Router = Router();
router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
return res.send(channel);
});
@@ -29,7 +31,7 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
if (channel.type === ChannelType.DM) {
- const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } });
+ const recipient = await Recipient.findOneOrFail({ where: { channel_id, user_id: req.user_id } });
recipient.closed = true;
await Promise.all([
recipient.save(),
@@ -47,38 +49,13 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request
res.send(channel);
});
-export interface ChannelModifySchema {
- /**
- * @maxLength 100
- */
- name?: string;
- type?: ChannelType;
- topic?: string;
- icon?: string | null;
- bitrate?: number;
- user_limit?: number;
- rate_limit_per_user?: number;
- position?: number;
- permission_overwrites?: {
- id: string;
- type: ChannelPermissionOverwriteType;
- allow: string;
- deny: string;
- }[];
- parent_id?: string;
- id?: string; // is not used (only for guild create)
- nsfw?: boolean;
- rtc_region?: string;
- default_auto_archive_duration?: number;
-}
-
router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- var payload = req.body as ChannelModifySchema;
+ let payload = req.body as ChannelModifySchema;
const { channel_id } = req.params;
if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
- const channel = await Channel.findOneOrFail({ id: channel_id });
- channel.assign(payload);
+ let channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ channel = OrmUtils.mergeDeep(channel, payload);
await Promise.all([
channel.save(),
diff --git a/api/src/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts
index 9c361164..b5c65c0d 100644
--- a/api/src/routes/channels/#channel_id/invites.ts
+++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -1,24 +1,13 @@
import { Router, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
import { random } from "@fosscord/api";
import { Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util";
import { isTextChannel } from "./messages";
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
-export interface InviteCreateSchema {
- target_user_id?: string;
- target_type?: string;
- validate?: string; // ? what is this
- max_age?: number;
- max_uses?: number;
- temporary?: boolean;
- unique?: boolean;
- target_user?: string;
- target_user_type?: number;
-}
-
router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }),
async (req: Request, res: Response) => {
const { user_id } = req;
@@ -33,21 +22,19 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
const expires_at = new Date(req.body.max_age * 1000 + Date.now());
- const invite = await new Invite({
- code: random(),
- temporary: req.body.temporary,
- uses: 0,
+ const invite = await OrmUtils.mergeDeep(new Invite(),{
+ temporary: req.body.temporary || true,
max_uses: req.body.max_uses,
max_age: req.body.max_age,
expires_at,
- created_at: new Date(),
guild_id,
- channel_id: channel_id,
+ channel_id,
inviter_id: user_id
}).save();
- const data = invite.toJSON();
+ //TODO: check this, removed toJSON call
+ const data = JSON.parse(JSON.stringify(invite));
data.inviter = await User.getPublicUser(req.user_id);
- data.guild = await Guild.findOne({ id: guild_id });
+ data.guild = await Guild.findOne({ where: { id: guild_id } });
data.channel = channel;
await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent);
@@ -55,9 +42,8 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
});
router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- const { user_id } = req;
const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
if (!channel.guild_id) {
throw new HTTPError("This channel doesn't exist", 404);
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index 885c5eca..041f4d5e 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -1,26 +1,18 @@
import { emitEvent, getPermission, MessageAckEvent, ReadState, Snowflake } from "@fosscord/util";
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
-// TODO: public read receipts & privacy scoping
-// TODO: send read state event to all channel members
-// TODO: advance-only notification cursor
-
-export interface MessageAcknowledgeSchema {
- manual?: boolean;
- mention_count?: number;
-}
-
router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
const permission = await getPermission(req.user_id, undefined, channel_id);
permission.hasThrow("VIEW_CHANNEL");
- let read_state = await ReadState.findOne({ user_id: req.user_id, channel_id });
- if (!read_state) read_state = new ReadState({ user_id: req.user_id, channel_id });
+ let read_state = await ReadState.findOne({ where: { user_id: req.user_id, channel_id } });
+ if (!read_state) read_state = OrmUtils.mergeDeep(new ReadState(), { user_id: req.user_id, channel_id }) as ReadState;
read_state.last_message_id = message_id;
await read_state.save();
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index b2cb6763..b2cb6763 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/crosspost.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index 6d2bf185..d7e27062 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -2,20 +2,24 @@ import {
Attachment,
Channel,
Embed,
+ DiscordApiErrors,
emitEvent,
+ FosscordApiErrors,
getPermission,
getRights,
Message,
MessageCreateEvent,
MessageDeleteEvent,
MessageUpdateEvent,
- uploadFile
+ Snowflake,
+ uploadFile,
+ MessageCreateSchema
} from "@fosscord/util";
import { Router, Response, Request } from "express";
import multer from "multer";
import { route } from "@fosscord/api";
import { handleMessage, postHandleMessage } from "@fosscord/api";
-import { MessageCreateSchema } from "../index";
+import { HTTPError } from "@fosscord/util";
const router = Router();
// TODO: message content/embed string length limit
@@ -31,7 +35,7 @@ const messageUpload = multer({
router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
- var body = req.body as MessageCreateSchema;
+ let body = req.body as MessageCreateSchema;
const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] });
@@ -88,20 +92,37 @@ router.put(
route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_BACKDATED_EVENTS" }),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
- var body = req.body as MessageCreateSchema;
+ let body = req.body as MessageCreateSchema;
const attachments: Attachment[] = [];
+
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("SEND_MESSAGES");
+
+ // regex to check if message contains anything other than numerals ( also no decimals )
+ if (!message_id.match(/^\+?\d+$/)) {
+ throw new HTTPError("Message IDs must be positive integers", 400);
+ }
+
+ const snowflake = Snowflake.deconstruct(message_id)
+ if (Date.now() < snowflake.timestamp) {
+ // message is in the future
+ throw FosscordApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
+ }
+
+ const exists = await Message.findOne({ where: { id: message_id, channel_id: channel_id }});
+ if (exists) {
+ throw FosscordApiErrors.CANNOT_REPLACE_BY_BACKFILL;
+ }
if (req.file) {
try {
- const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
+ const file: any = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
attachments.push({ ...file, proxy_url: file.url });
} catch (error) {
return res.status(400).json(error);
}
}
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
-
- // TODO: check the ID is not from the future, to prevent future-faking of channel histories
const embeds = body.embeds || [];
if (body.embed) embeds.push(body.embed);
@@ -115,11 +136,9 @@ router.put(
channel_id,
attachments,
edited_timestamp: undefined,
- timestamp: undefined, // FIXME: calculate timestamp from snowflake
+ timestamp: new Date(snowflake.timestamp),
});
- channel.last_message_id = message.id;
-
//Fix for the client bug
delete message.member
@@ -150,8 +169,8 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res:
router.delete("/", route({}), async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
- const message = await Message.findOneOrFail({ id: message_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const message = await Message.findOneOrFail({ where: { id: message_id } });
const rights = await getRights(req.user_id);
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index d93cf70f..d0ab35bb 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -15,7 +15,7 @@ import {
} from "@fosscord/util";
import { route } from "@fosscord/api";
import { Router, Response, Request } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { In } from "typeorm";
const router = Router();
@@ -39,7 +39,7 @@ function getEmoji(emoji: string): PartialEmoji {
router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
await Message.update({ id: message_id, channel_id }, { reactions: [] });
@@ -60,7 +60,7 @@ router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: R
const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji);
- const message = await Message.findOneOrFail({ id: message_id, channel_id });
+ const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
if (!already_added) throw new HTTPError("Reaction not found", 404);
@@ -87,7 +87,7 @@ router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request
const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji);
- const message = await Message.findOneOrFail({ id: message_id, channel_id });
+ const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
if (!reaction) throw new HTTPError("Reaction not found", 404);
@@ -106,14 +106,14 @@ router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right
if (user_id !== "@me") throw new HTTPError("Invalid user");
const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({ id: channel_id });
- const message = await Message.findOneOrFail({ id: message_id, channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
if (!already_added) req.permission!.hasThrow("ADD_REACTIONS");
if (emoji.id) {
- const external_emoji = await Emoji.findOneOrFail({ id: emoji.id });
+ const external_emoji = await Emoji.findOneOrFail({ where: { id: emoji.id } });
if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS");
emoji.animated = external_emoji.animated;
emoji.name = external_emoji.name;
@@ -126,7 +126,7 @@ router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right
await message.save();
- const member = channel.guild_id && (await Member.findOneOrFail({ id: req.user_id }));
+ const member = channel.guild_id && (await Member.findOneOrFail({ where: { id: req.user_id } }));
await emitEvent({
event: "MESSAGE_REACTION_ADD",
@@ -145,12 +145,12 @@ router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right
});
router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => {
- var { message_id, channel_id, user_id } = req.params;
+ let { message_id, channel_id, user_id } = req.params;
const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({ id: channel_id });
- const message = await Message.findOneOrFail({ id: message_id, channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
if (user_id === "@me") user_id = req.user_id;
else {
diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 7a711cb0..af44b522 100644
--- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
-import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { Channel, Config, emitEvent, getPermission, getRights, MessageDeleteBulkEvent, Message } from "@fosscord/util";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
import { In } from "typeorm";
@@ -8,28 +8,30 @@ const router: Router = Router();
export default router;
-export interface BulkDeleteSchema {
- messages: string[];
-}
-
-// TODO: should users be able to bulk delete messages or only bots?
-// TODO: should this request fail, if you provide messages older than 14 days/invalid ids?
+// should users be able to bulk delete messages or only bots? ANSWER: all users
+// should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => {
const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({where:{ id: channel_id} });
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");
+
+ let superuser = rights.has("MANAGE_MESSAGES");
const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
- permission.hasThrow("MANAGE_MESSAGES");
const { maxBulkDelete } = Config.get().limits.message;
const { messages } = req.body as { messages: string[] };
- if (messages.length < 2) throw new HTTPError("You must at least specify 2 messages to bulk delete");
- if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
+ if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
+ if (!superuser) {
+ permission.hasThrow("MANAGE_MESSAGES");
+ if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
+ }
- await Message.delete(messages.map((x) => ({ id: x })));
+ await Message.delete({ id: In(messages) });
await emitEvent({
event: "MESSAGE_DELETE_BULK",
diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 34cc5ff8..9ab0d97d 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -5,16 +5,17 @@ import {
ChannelType,
Config,
DmChannelDTO,
- Embed,
emitEvent,
getPermission,
getRights,
Message,
MessageCreateEvent,
+ Snowflake,
uploadFile,
- Member
+ Member,
+ MessageCreateSchema
} from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { handleMessage, postHandleMessage, route } from "@fosscord/api";
import multer from "multer";
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
@@ -30,6 +31,8 @@ export function isTextChannel(type: ChannelType): boolean {
case ChannelType.GUILD_VOICE:
case ChannelType.GUILD_STAGE_VOICE:
case ChannelType.GUILD_CATEGORY:
+ case ChannelType.GUILD_FORUM:
+ case ChannelType.DIRECTORY:
throw new HTTPError("not a text channel", 400);
case ChannelType.DM:
case ChannelType.GROUP_DM:
@@ -46,37 +49,11 @@ export function isTextChannel(type: ChannelType): boolean {
}
}
-export interface MessageCreateSchema {
- content?: string;
- nonce?: string;
- tts?: boolean;
- flags?: string;
- embeds?: Embed[];
- embed?: Embed;
- // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object)
- allowed_mentions?: {
- parse?: string[];
- roles?: string[];
- users?: string[];
- replied_user?: boolean;
- };
- message_reference?: {
- message_id: string;
- channel_id: string;
- guild_id?: string;
- fail_if_not_exists?: boolean;
- };
- payload_json?: string;
- file?: any;
- attachments?: any[]; //TODO we should create an interface for attachments
- sticker_ids?: string[];
-}
-
// https://discord.com/developers/docs/resources/channel#create-message
// get messages
router.get("/", async (req: Request, res: Response) => {
const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
if (!channel) throw new HTTPError("Channel not found", 404);
isTextChannel(channel.type);
@@ -84,23 +61,30 @@ router.get("/", async (req: Request, res: Response) => {
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");
+ if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
- var halfLimit = Math.floor(limit / 2);
+ let halfLimit = Math.floor(limit / 2);
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([]);
- var query: FindManyOptions<Message> & { where: { id?: any; }; } = {
+ let query: FindManyOptions<Message> & { where: { id?: any; }; } = {
order: { id: "DESC" },
take: limit,
where: { channel_id },
relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"]
};
+
- if (after) query.where.id = MoreThan(after);
- else if (before) query.where.id = LessThan(before);
+ if (after) {
+ if (after > new Snowflake()) return res.status(422);
+ query.where.id = MoreThan(after);
+ }
+ else if (before) {
+ if (before < req.params.channel_id) return res.status(422);
+ query.where.id = LessThan(before);
+ }
else if (around) {
query.where.id = [
MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
@@ -126,10 +110,13 @@ router.get("/", async (req: Request, res: Response) => {
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}`;
});
-
- //Some clients ( discord.js ) only check if a property exists within the response,
- //which causes erorrs when, say, the `application` property is `null`.
- for (var curr in x) {
+
+ /**
+ Some clients ( discord.js ) only check if a property exists within the response,
+ which causes erorrs when, say, the `application` property is `null`.
+ **/
+
+ for (let curr in x) {
if (x[curr] === null)
delete x[curr];
}
@@ -144,23 +131,22 @@ const messageUpload = multer({
limits: {
fileSize: 1024 * 1024 * 100,
fields: 10,
- files: 1
+ // files: 1
},
storage: multer.memoryStorage()
}); // max upload 50 mb
-
-// TODO: dynamically change limit of MessageCreateSchema with config
-// TODO: check: sum of all characters in an embed structure must not exceed instance limits
-
-// https://discord.com/developers/docs/resources/channel#create-message
-// TODO: text channel slowdown
-// TODO: trim and replace message content and every embed field
-// TODO: check allowed_mentions
-
+/**
+ TODO: dynamically change limit of MessageCreateSchema with config
+
+ https://discord.com/developers/docs/resources/channel#create-message
+ TODO: text channel slowdown (per-user and across-users)
+ Q: trim and replace message content and every embed field A: NO, given this cannot be implemented in E2EE channels
+ TODO: only dispatch notifications for mentions denoted in allowed_mentions
+**/
// Send message
router.post(
"/",
- messageUpload.single("file"),
+ messageUpload.any(),
async (req, res, next) => {
if (req.body.payload_json) {
req.body = JSON.parse(req.body.payload_json);
@@ -171,21 +157,24 @@ router.post(
route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
- var body = req.body as MessageCreateSchema;
+ let body = req.body as MessageCreateSchema;
const attachments: Attachment[] = [];
- if (req.file) {
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
+ if (!channel.isWritable()) {
+ throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400)
+ }
+
+ const files = req.files as Express.Multer.File[] ?? [];
+ for (let currFile of files) {
try {
- const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
+ const file: any = await uploadFile(`/attachments/${channel.id}`, currFile);
attachments.push({ ...file, proxy_url: file.url });
- } catch (error) {
+ }
+ catch (error) {
return res.status(400).json(error);
}
}
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
- if (!channel.isWritable()) {
- throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400)
- }
const embeds = body.embeds || [];
if (body.embed) embeds.push(body.embed);
@@ -223,11 +212,19 @@ router.post(
})
);
}
-
-
- //Fix for the client bug
- delete message.member
+ //Defining member fields
+ var member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] });
+ // TODO: This doesn't work either
+ // member.roles = member.roles.filter((role) => {
+ // return role.id !== role.guild_id;
+ // }).map((role) => {
+ // return role.id;
+ // });
+ message.member = member;
+ // TODO: Figure this out
+ // delete message.member.last_message_id;
+ // delete message.member.index;
await Promise.all([
message.save(),
@@ -241,3 +238,4 @@ router.post(
return res.json(message);
}
);
+
diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index 2eded853..34052fe5 100644
--- a/api/src/routes/channels/#channel_id/permissions.ts
+++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -1,6 +1,7 @@
import {
Channel,
ChannelPermissionOverwrite,
+ ChannelPermissionOverwriteSchema,
ChannelPermissionOverwriteType,
ChannelUpdateEvent,
emitEvent,
@@ -9,14 +10,10 @@ import {
Role
} from "@fosscord/util";
import { Router, Response, Request } from "express";
-import { HTTPError } from "lambert-server";
-
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
-const router: Router = Router();
-// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel)
-
-export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite {}
+const router: Router = Router();
router.put(
"/:overwrite_id",
@@ -25,17 +22,17 @@ router.put(
const { channel_id, overwrite_id } = req.params;
const body = req.body as ChannelPermissionOverwriteSchema;
- var channel = await Channel.findOneOrFail({ id: channel_id });
+ let channel = await Channel.findOneOrFail({ where: {id: channel_id} });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
if (body.type === 0) {
- if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404);
+ if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
} else if (body.type === 1) {
- if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404);
+ if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
} else throw new HTTPError("type not supported", 501);
// @ts-ignore
- var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id);
+ let overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id);
if (!overwrite) {
// @ts-ignore
overwrite = {
@@ -64,7 +61,7 @@ router.put(
router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id);
diff --git a/api/src/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index e71e659f..003638c5 100644
--- a/api/src/routes/channels/#channel_id/pins.ts
+++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -9,7 +9,7 @@ import {
DiscordApiErrors
} from "@fosscord/util";
import { Router, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -17,12 +17,12 @@ const router: Router = Router();
router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({ id: message_id });
+ const message = await Message.findOneOrFail({ where: { id: message_id } });
// * in dm channels anyone can pin messages -> only check for guilds
if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
- const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true });
+ const pinned_count = await Message.count({ where: { channel: { id: channel_id }, pinned: true } });
const { maxPins } = Config.get().limits.channel;
if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
@@ -50,10 +50,10 @@ router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Re
router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
- const message = await Message.findOneOrFail({ id: message_id });
+ const message = await Message.findOneOrFail({ where: { id: message_id } });
message.pinned = false;
await Promise.all([
@@ -82,7 +82,7 @@ router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req:
router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => {
const { channel_id } = req.params;
- let pins = await Message.find({ channel_id: channel_id, pinned: true });
+ let pins = await Message.find({ where: { channel_id, pinned: true } });
res.send(pins);
});
diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index e6466211..069212e2 100644
--- a/api/src/routes/channels/#channel_id/recipients.ts
+++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -11,6 +11,7 @@ import {
User
} from "@fosscord/util";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
@@ -28,7 +29,7 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
}
- channel.recipients!.push(new Recipient({ channel_id: channel_id, user_id: user_id }));
+ channel.recipients!.push(OrmUtils.mergeDeep(new Recipient(), { channel_id, user_id: user_id }));
await channel.save();
await emitEvent({
diff --git a/api/src/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 56652368..99460f6e 100644
--- a/api/src/routes/channels/#channel_id/typing.ts
+++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -8,7 +8,7 @@ router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, re
const { channel_id } = req.params;
const user_id = req.user_id;
const timestamp = Date.now();
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
const member = await Member.findOne({ where: { id: user_id, guild_id: channel.guild_id }, relations: ["roles", "user"] });
await emitEvent({
diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 92895da6..b11c8eb9 100644
--- a/api/src/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -1,19 +1,11 @@
import { Router, Response, Request } from "express";
import { route } from "@fosscord/api";
import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { isTextChannel } from "./messages/index";
import { DiscordApiErrors } from "@fosscord/util";
const router: Router = Router();
-// TODO: webhooks
-export interface WebhookCreateSchema {
- /**
- * @maxLength 80
- */
- name: string;
- avatar: string;
-}
//TODO: implement webhooks
router.get("/", route({}), async (req: Request, res: Response) => {
res.json([]);
@@ -22,20 +14,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// TODO: use Image Data Type for avatar instead of String
router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => {
const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
isTextChannel(channel.type);
if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
- const webhook_count = await Webhook.count({ channel_id });
+ const webhook_count = await Webhook.count({ where: { channel_id } });
const { maxWebhooks } = Config.get().limits.channel;
if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
- var { avatar, name } = req.body as { name: string; avatar?: string };
+ let { avatar, name } = req.body as { name: string; avatar?: string };
name = trimSpecial(name);
if (name === "clyde") throw new HTTPError("Invalid name", 400);
// TODO: save webhook in database and send response
+ res.json(new Webhook());
});
export default router;
diff --git a/api/src/routes/discovery.ts b/src/api/routes/discovery.ts
index 1991400e..30c418c6 100644
--- a/api/src/routes/discovery.ts
+++ b/src/api/routes/discovery.ts
@@ -1,6 +1,6 @@
import { Categories } from "@fosscord/util";
import { Router, Response, Request } from "express";
-import { route } from "@fosscord/api";
+import { route } from "..";
const router = Router();
@@ -10,7 +10,7 @@ router.get("/categories", route({}), async (req: Request, res: Response) => {
const { locale, 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);
});
diff --git a/api/src/routes/downloads.ts b/src/api/routes/downloads.ts
index ddfc080c..44530353 100644
--- a/api/src/routes/downloads.ts
+++ b/src/api/routes/downloads.ts
@@ -1,5 +1,5 @@
import { Router, Response, Request } from "express";
-import { route } from "@fosscord/api";
+import { route } from "..";
import { Release, Config } from "@fosscord/util";
const router = Router();
@@ -12,7 +12,7 @@ router.get("/:branch", route({}), async (req: Request, res: Response) => {
if(!platform || !["linux", "osx", "win"].includes(platform.toString())) return res.status(404)
- const release = await Release.findOneOrFail({ name: client.releases.upstreamVersion });
+ const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } });
res.redirect(release[`win_url`]);
});
diff --git a/api/src/routes/experiments.ts b/src/api/routes/experiments.ts
index 7be86fb8..fcbd9271 100644
--- a/api/src/routes/experiments.ts
+++ b/src/api/routes/experiments.ts
@@ -1,5 +1,5 @@
import { Router, Response, Request } from "express";
-import { route } from "@fosscord/api";
+import { route } from "..";
const router = Router();
diff --git a/api/src/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index f1dbb9df..f1dbb9df 100644
--- a/api/src/routes/gateway/bot.ts
+++ b/src/api/routes/gateway/bot.ts
diff --git a/api/src/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 9bad7478..9bad7478 100644
--- a/api/src/routes/gateway/index.ts
+++ b/src/api/routes/gateway/index.ts
diff --git a/api/src/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index 9ad7a592..1099dc4a 100644
--- a/api/src/routes/gifs/search.ts
+++ b/src/api/routes/gifs/search.ts
@@ -20,7 +20,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
headers: { "Content-Type": "application/json" }
});
- const { results } = await response.json();
+ const { results } = await response.json() as any;
res.json(results.map(parseGifResult)).status(200);
});
diff --git a/api/src/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index 6d97bf7c..2b28d9d2 100644
--- a/api/src/routes/gifs/trending-gifs.ts
+++ b/src/api/routes/gifs/trending-gifs.ts
@@ -20,7 +20,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
headers: { "Content-Type": "application/json" }
});
- const { results } = await response.json();
+ const { results } = await response.json() as any;
res.json(results.map(parseGifResult)).status(200);
});
diff --git a/api/src/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index c81b4c08..61eb76c4 100644
--- a/api/src/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -3,7 +3,7 @@ import fetch from "node-fetch";
import ProxyAgent from 'proxy-agent';
import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
const router = Router();
@@ -50,8 +50,8 @@ router.get("/", route({}), async (req: Request, res: Response) => {
})
]);
- const { tags } = await responseSource.json();
- const { results } = await trendGifSource.json();
+ const { tags } = await responseSource.json() as any;
+ const { results } = await trendGifSource.json() as any;
res.json({
categories: tags.map((x: any) => ({ name: x.searchterm, src: x.image })),
diff --git a/api/src/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index 1432f39c..bd0140d6 100644
--- a/api/src/routes/guild-recommendations.ts
+++ b/src/api/routes/guild-recommendations.ts
@@ -1,13 +1,14 @@
import { Guild, Config } from "@fosscord/util";
import { Router, Request, Response } from "express";
-import { route } from "@fosscord/api";
+import { route } from "..";
+import {Like} from "typeorm"
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { limit, personalization_disabled } = req.query;
- var showAllGuilds = Config.get().guild.discovery.showAllGuilds;
+ let showAllGuilds = Config.get().guild.discovery.showAllGuilds;
// ! this only works using SQL querys
// TODO: implement this with default typeorm query
// const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) });
@@ -16,7 +17,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const guilds = showAllGuilds
? await Guild.find({ take: Math.abs(Number(limit || 24)) })
- : await Guild.find({ where: `"features" LIKE '%DISCOVERABLE%'`, take: Math.abs(Number(limit || 24)) });
+ : await Guild.find({ where: { features: Like('%DISCOVERABLE%') }, take: Math.abs(Number(limit || 24)) });
res.send({ recommended_guilds: guilds, load_id: `server_recs/${genLoadId(32)}`}).status(200);
});
diff --git a/api/src/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index a4f2f800..b54835fc 100644
--- a/api/src/routes/guilds/#guild_id/audit-logs.ts
+++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -1,8 +1,5 @@
import { Router, Response, Request } from "express";
-import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
-import { ChannelModifySchema } from "../../channels/#channel_id";
const router = Router();
//TODO: implement audit logs
diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index 1ce41936..3d405344 100644
--- a/api/src/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -1,29 +1,8 @@
import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { DiscordApiErrors, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member, BanRegistrySchema, BanModeratorSchema } from "@fosscord/util";
+import { HTTPError } from "@fosscord/util";
import { getIpAdress, route } from "@fosscord/api";
-
-export interface BanCreateSchema {
- delete_message_days?: string;
- reason?: string;
-};
-
-export interface BanRegistrySchema {
- id: string;
- user_id: string;
- guild_id: string;
- executor_id: string;
- ip?: string;
- reason?: string | undefined;
-};
-
-export interface BanModeratorSchema {
- id: string;
- user_id: string;
- guild_id: string;
- executor_id: string;
- reason?: string | undefined;
-};
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
@@ -32,7 +11,7 @@ const router: Router = Router();
router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- let bans = await Ban.find({ guild_id: guild_id });
+ let bans = await Ban.find({ where: { guild_id } });
let promisesToAwait: object[] = [];
const bansObj: object[] = [];
@@ -65,7 +44,7 @@ router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request,
const { guild_id } = req.params;
const user_id = req.params.ban;
- let ban = await Ban.findOneOrFail({ guild_id: guild_id, user_id: user_id }) as BanRegistrySchema;
+ let ban = await Ban.findOneOrFail({ where: { guild_id, user_id } }) as BanRegistrySchema;
if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
// pretend self-bans don't exist to prevent victim chasing
@@ -90,7 +69,7 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER
const banned_user = await User.getPublicUser(banned_user_id);
- const ban = new Ban({
+ const ban = OrmUtils.mergeDeep(new Ban(),{
user_id: banned_user_id,
guild_id: guild_id,
ip: getIpAdress(req),
@@ -122,7 +101,7 @@ router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res:
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);
- const ban = new Ban({
+ const ban = OrmUtils.mergeDeep(new Ban(), {
user_id: req.params.user_id,
guild_id: guild_id,
ip: getIpAdress(req),
@@ -149,7 +128,7 @@ router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res:
router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
const { guild_id, user_id } = req.params;
- let ban = await Ban.findOneOrFail({ guild_id: guild_id, user_id: user_id });
+ let ban = await Ban.findOneOrFail({ where: { guild_id, user_id } });
if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
// make self-bans irreversible and hide them from view to avoid victim chasing
diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index a921fa21..8f2d3643 100644
--- a/api/src/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -1,13 +1,12 @@
import { Router, Response, Request } from "express";
-import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { Channel, ChannelUpdateEvent, getPermission, emitEvent, ChannelModifySchema, ChannelReorderSchema } from "@fosscord/util";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { ChannelModifySchema } from "../../channels/#channel_id";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- const channels = await Channel.find({ guild_id });
+ const channels = await Channel.find({ where: { guild_id } });
res.json(channels);
});
@@ -22,8 +21,6 @@ router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNE
res.status(201).json(channel);
});
-export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[];
-
router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
// changes guild channel position
const { guild_id } = req.params;
@@ -48,7 +45,7 @@ router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHAN
}
await Channel.update({ guild_id, id: x.id }, opts);
- const channel = await Channel.findOneOrFail({ guild_id, id: x.id });
+ const channel = await Channel.findOneOrFail({ where: { guild_id, id: x.id } });
await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent);
})
diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index bd158c56..e2624651 100644
--- a/api/src/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -1,6 +1,6 @@
import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util";
import { Router, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
@@ -8,7 +8,7 @@ const router = Router();
// discord prefixes this route with /delete instead of using the delete method
// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
router.post("/", route({}), async (req: Request, res: Response) => {
- var { guild_id } = req.params;
+ let { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] });
if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401);
diff --git a/api/src/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index ad20633f..ad20633f 100644
--- a/api/src/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
diff --git a/api/src/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index 85d7ac05..4bf4bdcd 100644
--- a/api/src/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -1,21 +1,10 @@
import { Router, Request, Response } from "express";
-import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User } from "@fosscord/util";
+import { Config, DiscordApiErrors, emitEvent, Emoji, EmojiCreateSchema, EmojiModifySchema, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User } from "@fosscord/util";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
-export interface EmojiCreateSchema {
- name?: string;
- image: string;
- require_colons?: boolean | null;
- roles?: string[];
-}
-
-export interface EmojiModifySchema {
- name?: string;
- roles?: string[];
-}
-
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
@@ -41,16 +30,16 @@ router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_A
const body = req.body as EmojiCreateSchema;
const id = Snowflake.generate();
- const emoji_count = await Emoji.count({ guild_id: guild_id });
+ const emoji_count = await Emoji.count({ where: { guild_id } });
const { maxEmojis } = Config.get().limits.guild;
if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
if (body.require_colons == null) body.require_colons = true;
- const user = await User.findOneOrFail({ id: req.user_id });
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
- const emoji = await new Emoji({
+ const emoji = await OrmUtils.mergeDeep(new Emoji(), {
id: id,
guild_id: guild_id,
...body,
@@ -66,7 +55,7 @@ router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_A
guild_id: guild_id,
data: {
guild_id: guild_id,
- emojis: await Emoji.find({ guild_id: guild_id })
+ emojis: await Emoji.find({ where: { guild_id } })
}
} as GuildEmojisUpdateEvent);
@@ -80,14 +69,14 @@ router.patch(
const { emoji_id, guild_id } = req.params;
const body = req.body as EmojiModifySchema;
- const emoji = await new Emoji({ ...body, id: emoji_id, guild_id: guild_id }).save();
+ const emoji = await OrmUtils.mergeDeep(new Emoji(), { ...body, id: emoji_id, guild_id: guild_id }).save();
await emitEvent({
event: "GUILD_EMOJIS_UPDATE",
guild_id: guild_id,
data: {
guild_id: guild_id,
- emojis: await Emoji.find({ guild_id: guild_id })
+ emojis: await Emoji.find({ where: { guild_id } })
}
} as GuildEmojisUpdateEvent);
@@ -108,7 +97,7 @@ router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
guild_id: guild_id,
data: {
guild_id: guild_id,
- emojis: await Emoji.find({ guild_id: guild_id })
+ emojis: await Emoji.find({ where: { guild_id } })
}
} as GuildEmojisUpdateEvent);
diff --git a/api/src/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index 4ec3df72..a9712c71 100644
--- a/api/src/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -1,33 +1,17 @@
import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, GuildUpdateSchema, handleFile, Member } from "@fosscord/util";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
-import "missing-native-js-functions";
-import { GuildCreateSchema } from "../index";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
-export interface GuildUpdateSchema extends Omit<GuildCreateSchema, "channels"> {
- banner?: string | null;
- splash?: string | null;
- description?: string;
- features?: string[];
- verification_level?: number;
- default_message_notifications?: number;
- system_channel_flags?: number;
- explicit_content_filter?: number;
- public_updates_channel_id?: string;
- afk_timeout?: number;
- afk_channel_id?: string;
- preferred_locale?: string;
-}
-
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const [guild, member] = await Promise.all([
- Guild.findOneOrFail({ id: guild_id }),
- Member.findOne({ guild_id: guild_id, id: req.user_id })
+ Guild.findOneOrFail({ where: { id: guild_id } }),
+ Member.findOne({ where: { 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);
@@ -54,14 +38,15 @@ router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res:
if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
- var guild = await Guild.findOneOrFail({
+ let guild = await Guild.findOneOrFail({
where: { id: guild_id },
relations: ["emojis", "roles", "stickers"]
});
// TODO: check if body ids are valid
- guild.assign(body);
+ guild = OrmUtils.mergeDeep(guild, body);
- const data = guild.toJSON();
+ //TODO: check this, removed toJSON call
+ const data = JSON.parse(JSON.stringify(guild));
// TODO: guild hashes
// TODO: fix vanity_url_code, template_id
delete data.vanity_url_code;
diff --git a/api/src/routes/guilds/#guild_id/integrations.ts b/src/api/routes/guilds/#guild_id/integrations.ts
index abf997c9..90650111 100644
--- a/api/src/routes/guilds/#guild_id/integrations.ts
+++ b/src/api/routes/guilds/#guild_id/integrations.ts
@@ -1,8 +1,7 @@
import { Router, Response, Request } from "express";
import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { ChannelModifySchema } from "../../channels/#channel_id";
const router = Router();
//TODO: implement integrations list
diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index b7534e31..b7534e31 100644
--- a/api/src/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 34836292..794369d8 100644
--- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -1,19 +1,16 @@
import { Request, Response, Router } from "express";
-import { Member, getPermission, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Guild } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Rights, Guild, MemberChangeSchema } from "@fosscord/util";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
-export interface MemberChangeSchema {
- roles?: string[];
-}
-
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const member = await Member.findOneOrFail({ id: member_id, guild_id });
+ const member = await Member.findOneOrFail({ where: { id: member_id, guild_id } });
return res.json(member);
});
@@ -25,13 +22,13 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re
const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] });
const permission = await getPermission(req.user_id, guild_id);
- const everyone = await Role.findOneOrFail({ guild_id: guild_id, name: "@everyone", position: 0 });
+ const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } });
if (body.roles) {
permission.hasThrow("MANAGE_ROLES");
if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
- member.roles = body.roles.map((x) => new Role({ id: x })); // foreign key constraint will fail if role doesn't exist
+ member.roles = body.roles.map((x) => OrmUtils.mergeDeep(new Role(), { id: x })); // foreign key constraint will fail if role doesn't exist
}
await member.save();
@@ -52,27 +49,47 @@ router.put("/", route({}), async (req: Request, res: Response) => {
// TODO: Lurker mode
+ const rights = await getRights(req.user_id);
+
let { guild_id, member_id } = req.params;
- if (member_id === "@me") member_id = req.user_id;
+ if (member_id === "@me") {
+ member_id = req.user_id;
+ rights.hasThrow("JOIN_GUILDS");
+ } else {
+ // TODO: join others by controller
+ }
+
+ let guild = await Guild.findOneOrFail({
+ where: { id: guild_id }
+ });
- var guild = await Guild.findOneOrFail({
- where: { id: guild_id } });
+ let emoji = await Emoji.find({
+ where: { guild_id: guild_id }
+ });
- var emoji = await Emoji.find({
- where: { guild_id: guild_id } });
+ let roles = await Role.find({
+ where: { guild_id: guild_id }
+ });
- var roles = await Role.find({
- where: { guild_id: guild_id } });
+ let stickers = await Sticker.find({
+ where: { guild_id: guild_id }
+ });
- var stickers = await Sticker.find({
- where: { guild_id: guild_id } });
-
await Member.addToGuild(member_id, guild_id);
- res.send({...guild, emojis: emoji, roles: roles, stickers: stickers});
+ res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
});
-router.delete("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res: Response) => {
+router.delete("/", route({}), async (req: Request, res: Response) => {
+ const permission = await getPermission(req.user_id);
+ const rights = await getRights(req.user_id);
const { guild_id, member_id } = req.params;
+ if (member_id !== "@me" || member_id === req.user_id) {
+ // TODO: unless force-joined
+ rights.hasThrow("SELF_LEAVE_GROUPS");
+ } else {
+ rights.hasThrow("KICK_BAN_MEMBERS");
+ permission.hasThrow("KICK_MEMBERS");
+ }
await Member.removeFromGuild(member_id, guild_id);
res.sendStatus(204);
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 27f7f65d..a6c71333 100644
--- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -4,13 +4,9 @@ import { Request, Response, Router } from "express";
const router = Router();
-export interface MemberNickChangeSchema {
- nick: string;
-}
-
router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => {
- var { guild_id, member_id } = req.params;
- var permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
+ let { guild_id, member_id } = req.params;
+ let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
if (member_id === "@me") {
member_id = req.user_id;
permissionString = "CHANGE_NICKNAME";
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 8f5ca7ba..8f5ca7ba 100644
--- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index b730a4e7..2ed28bda 100644
--- a/api/src/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -2,7 +2,7 @@ import { Request, Response, Router } from "express";
import { Guild, Member, PublicMemberProjection } from "@fosscord/util";
import { route } from "@fosscord/api";
import { MoreThan } from "typeorm";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
const router = Router();
diff --git a/api/src/routes/guilds/#guild_id/premium.ts b/src/api/routes/guilds/#guild_id/premium.ts
index 75361ac6..75361ac6 100644
--- a/api/src/routes/guilds/#guild_id/premium.ts
+++ b/src/api/routes/guilds/#guild_id/premium.ts
diff --git a/api/src/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 0dd4d610..673f022f 100644
--- a/api/src/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -6,12 +6,16 @@ const router = Router();
//Returns all inactive members, respecting role hierarchy
export const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
- var date = new Date();
+ let date = new Date();
date.setDate(date.getDate() - days);
//Snowflake should have `generateFromTime` method? Or similar?
- var minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
+ let minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
- var members = await Member.find({
+ /**
+ idea: ability to customise the cutoff variable
+ possible candidates: public read receipt, last presence, last VC leave
+ **/
+ let members = await Member.find({
where: [
{
guild_id,
@@ -29,7 +33,7 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n
//I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
- const me = await Member.findOneOrFail({ id: user_id, guild_id }, { relations: ["roles"] });
+ const me = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["roles"] });
const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@@ -47,10 +51,10 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n
return members;
};
-router.get("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
const days = parseInt(req.query.days as string);
- var roles = req.query.include_roles;
+ 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[]);
@@ -58,17 +62,10 @@ router.get("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res:
res.send({ pruned: members.length });
});
-export interface PruneSchema {
- /**
- * @min 0
- */
- days: number;
-}
-
-router.post("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res: Response) => {
+router.post("/", route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }), async (req: Request, res: Response) => {
const days = parseInt(req.body.days);
- var roles = req.query.include_roles;
+ let roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles];
const { guild_id } = req.params;
diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index 75d24fd1..308d5ee5 100644
--- a/api/src/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -7,7 +7,7 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ 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")));
});
diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index b6894e3f..17f0b5e9 100644
--- a/api/src/routes/guilds/#guild_id/roles.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -9,35 +9,22 @@ import {
emitEvent,
Config,
DiscordApiErrors,
- handleFile
+ handleFile,
+ RoleModifySchema,
+ RolePositionUpdateSchema
} from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
-export interface RoleModifySchema {
- name?: string;
- permissions?: string;
- color?: number;
- hoist?: boolean; // whether the role should be displayed separately in the sidebar
- mentionable?: boolean; // whether the role should be mentionable
- position?: number;
- icon?: string;
- unicode_emoji?: string;
-}
-
-export type RolePositionUpdateSchema = {
- id: string;
- position: number;
-}[];
-
router.get("/", route({}), async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const roles = await Role.find({ guild_id: guild_id });
+ const roles = await Role.find({ where: { guild_id } });
return res.json(roles);
});
@@ -46,12 +33,12 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" })
const guild_id = req.params.guild_id;
const body = req.body as RoleModifySchema;
- const role_count = await Role.count({ guild_id });
+ 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);
- const role = new Role({
+ let role: Role = OrmUtils.mergeDeep(new Role(),{
// values before ...body are default and can be overriden
position: 0,
hoist: false,
@@ -81,59 +68,6 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" })
res.json(role);
});
-router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const { role_id } = req.params;
- if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
-
- await Promise.all([
- Role.delete({
- id: role_id,
- guild_id: guild_id
- }),
- emitEvent({
- event: "GUILD_ROLE_DELETE",
- guild_id,
- data: {
- guild_id,
- role_id
- }
- } as GuildRoleDeleteEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-// TODO: check role hierarchy
-
-router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { role_id, guild_id } = req.params;
- const body = req.body as RoleModifySchema;
-
- if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
-
- const role = new Role({
- ...body,
- id: role_id,
- guild_id,
- permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0"))
- });
-
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
- guild_id,
- data: {
- guild_id,
- role
- }
- } as GuildRoleUpdateEvent)
- ]);
-
- res.json(role);
-});
-
router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const body = req.body as RolePositionUpdateSchema;
diff --git a/api/src/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 4ea1dce1..71c9dfcd 100644
--- a/api/src/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -3,6 +3,7 @@ import {
GuildStickersUpdateEvent,
handleFile,
Member,
+ ModifyGuildStickerSchema,
Snowflake,
Sticker,
StickerFormatType,
@@ -12,14 +13,15 @@ import {
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
import multer from "multer";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(await Sticker.find({ guild_id }));
+ res.json(await Sticker.find({ where: { guild_id } }));
});
const bodyParser = multer({
@@ -43,7 +45,7 @@ router.post(
const id = Snowflake.generate();
const [sticker] = await Promise.all([
- new Sticker({
+ OrmUtils.mergeDeep(new Sticker(), {
...body,
guild_id,
id,
@@ -79,25 +81,9 @@ router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(await Sticker.findOneOrFail({ guild_id, id: sticker_id }));
+ res.json(await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }));
});
-export interface ModifyGuildStickerSchema {
- /**
- * @minLength 2
- * @maxLength 30
- */
- name: string;
- /**
- * @maxLength 100
- */
- description?: string;
- /**
- * @maxLength 200
- */
- tags: string;
-}
-
router.patch(
"/:sticker_id",
route({ body: "ModifyGuildStickerSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }),
@@ -105,7 +91,7 @@ router.patch(
const { guild_id, sticker_id } = req.params;
const body = req.body as ModifyGuildStickerSchema;
- const sticker = await new Sticker({ ...body, guild_id, id: sticker_id }).save();
+ const sticker = await OrmUtils.mergeDeep(new Sticker(), { ...body, guild_id, id: sticker_id }).save();
await sendStickerUpdateEvent(guild_id);
return res.json(sticker);
@@ -118,7 +104,7 @@ async function sendStickerUpdateEvent(guild_id: string) {
guild_id: guild_id,
data: {
guild_id: guild_id,
- stickers: await Sticker.find({ guild_id: guild_id })
+ stickers: await Sticker.find({ where: { guild_id } })
}
} as GuildStickersUpdateEvent);
}
diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index 5179e761..9c79692d 100644
--- a/api/src/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -1,8 +1,9 @@
import { Request, Response, Router } from "express";
import { Guild, Template } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
import { generateCode } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
@@ -23,20 +24,10 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"icon"
];
-export interface TemplateCreateSchema {
- name: string;
- description?: string;
-}
-
-export interface TemplateModifySchema {
- name: string;
- description?: string;
-}
-
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- var templates = await Template.find({ source_guild_id: guild_id });
+ let templates = await Template.find({ where: { source_guild_id: guild_id } });
return res.json(templates);
});
@@ -44,10 +35,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
- const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {});
+ const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => {});
if (exists) throw new HTTPError("Template already exists", 400);
- const template = await new Template({
+ const template = await OrmUtils.mergeDeep(new Template(), {
...req.body,
code: generateCode(),
creator_id: req.user_id,
@@ -75,7 +66,7 @@ router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request,
const { code, guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
- const template = await new Template({ code, serialized_source_guild: guild }).save();
+ const template = await OrmUtils.mergeDeep(new Template(), { code, serialized_source_guild: guild }).save();
res.json(template);
});
@@ -84,7 +75,7 @@ router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE
const { code, guild_id } = req.params;
const { name, description } = req.body;
- const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save();
+ const template = await OrmUtils.mergeDeep(new Template(), { code, name: name, description: description, source_guild_id: guild_id }).save();
res.json(template);
});
diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index 29cd25e2..ff92ce8d 100644
--- a/api/src/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -1,7 +1,8 @@
-import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util";
+import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial, VanityUrlSchema } from "@fosscord/util";
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
@@ -9,7 +10,7 @@ const InviteRegex = /\W/g;
router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (!guild.features.includes("ALIASABLE_NAMES")) {
const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } });
@@ -24,30 +25,22 @@ router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res:
}
});
-export interface VanityUrlSchema {
- /**
- * @minLength 1
- * @maxLength 20
- */
- code?: string;
-}
-
router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const body = req.body as VanityUrlSchema;
const code = body.code?.replace(InviteRegex, "");
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
- const invite = await Invite.findOne({ code });
+ const invite = await Invite.findOne({ where: { code } });
if (invite) throw new HTTPError("Invite already exists");
- const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT });
+ const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } });
- await new Invite({
+ await OrmUtils.mergeDeep(new Invite(), {
vanity_url: true,
code: code,
temporary: false,
@@ -60,7 +53,7 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" })
channel_id: id
}).save();
- return res.json({ code: code });
+ return res.json({ where: { code } });
});
export default router;
diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index f9fbea54..28a9e8c1 100644
--- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -1,23 +1,12 @@
-import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
+import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent, VoiceStateUpdateSchema } from "@fosscord/util";
import { route } from "@fosscord/api";
import { Request, Response, Router } from "express";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
-//TODO need more testing when community guild and voice stage channel are working
-
-export interface VoiceStateUpdateSchema {
- channel_id: string;
- guild_id?: string;
- suppress?: boolean;
- request_to_speak_timestamp?: Date;
- self_mute?: boolean;
- self_deaf?: boolean;
- self_video?: boolean;
-}
-
router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema;
- var { guild_id, user_id } = req.params;
+ let { guild_id, user_id } = req.params;
if (user_id === "@me") user_id = req.user_id;
const perms = await getPermission(req.user_id, guild_id, body.channel_id);
@@ -33,15 +22,17 @@ router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request
if (!body.suppress) body.request_to_speak_timestamp = new Date();
if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
- const voice_state = await VoiceState.findOne({
- guild_id,
- channel_id: body.channel_id,
- user_id
+ let voice_state = await VoiceState.findOne({
+ where: {
+ guild_id,
+ channel_id: body.channel_id,
+ user_id
+ }
});
if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
- voice_state.assign(body);
- const channel = await Channel.findOneOrFail({ guild_id, id: body.channel_id });
+ voice_state = OrmUtils.mergeDeep(voice_state, body) as VoiceState;
+ const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } });
if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
}
diff --git a/api/src/routes/guilds/#guild_id/webhooks.ts b/src/api/routes/guilds/#guild_id/webhooks.ts
index 8b2febea..c8c1eb5c 100644
--- a/api/src/routes/guilds/#guild_id/webhooks.ts
+++ b/src/api/routes/guilds/#guild_id/webhooks.ts
@@ -1,8 +1,7 @@
import { Router, Response, Request } from "express";
import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { ChannelModifySchema } from "../../channels/#channel_id";
const router = Router();
//TODO: implement webhooks
diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/src/api/routes/guilds/#guild_id/welcome_screen.ts
index 7141f17e..d08300ba 100644
--- a/api/src/routes/guilds/#guild_id/welcome_screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome_screen.ts
@@ -1,25 +1,14 @@
import { Request, Response, Router } from "express";
-import { Guild, getPermission, Snowflake, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { Guild, getPermission, Snowflake, Member, GuildUpdateWelcomeScreenSchema } from "@fosscord/util";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
-export interface GuildUpdateWelcomeScreenSchema {
- welcome_channels?: {
- channel_id: string;
- description: string;
- emoji_id?: string;
- emoji_name: string;
- }[];
- enabled?: boolean;
- description?: string;
-}
-
router.get("/", route({}), async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(guild.welcome_screen);
@@ -29,7 +18,7 @@ router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "M
const guild_id = req.params.guild_id;
const body = req.body as GuildUpdateWelcomeScreenSchema;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400);
if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index c31519fa..37739418 100644
--- a/api/src/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -1,7 +1,8 @@
import { Request, Response, Router } from "express";
import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { random, route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router: Router = Router();
@@ -17,11 +18,11 @@ const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
// Fetch existing widget invite for widget channel
- var invite = await Invite.findOne({ channel_id: guild.widget_channel_id });
+ let invite = await Invite.findOne({ where: { channel_id: guild.widget_channel_id } });
if (guild.widget_channel_id && !invite) {
// Create invite for channel if none exists
@@ -41,7 +42,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
inviter_id: null
};
- invite = await new Invite(body).save();
+ invite = await OrmUtils.mergeDeep(new Invite(), body).save();
}
// Fetch voice channels, and the @everyone permissions object
@@ -63,7 +64,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// Fetch members
// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
- let members = await Member.find({ guild_id: guild_id });
+ let members = await Member.find({ where: { guild_id } });
// Construct object to respond with
const data = {
diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index 4c82b740..a61d938d 100644
--- a/api/src/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -1,6 +1,6 @@
import { Request, Response, Router } from "express";
import { Guild } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
import fs from "fs";
import path from "path";
@@ -14,7 +14,7 @@ const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
// Fetch guild information
@@ -34,7 +34,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const sizeOf = require("image-size");
// TODO: Widget style templates need Fosscord 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);
}
diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index 2640618d..dbb4cc0c 100644
--- a/api/src/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -1,19 +1,14 @@
import { Request, Response, Router } from "express";
-import { Guild } from "@fosscord/util";
+import { Guild, WidgetModifySchema } from "@fosscord/util";
import { route } from "@fosscord/api";
-export interface WidgetModifySchema {
- enabled: boolean; // whether the widget is enabled
- channel_id: string; // the widget channel id
-}
-
const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ id: guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null });
});
diff --git a/api/src/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 10721413..e4d66192 100644
--- a/api/src/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -1,30 +1,16 @@
import { Router, Request, Response } from "express";
-import { Role, Guild, Snowflake, Config, getRights, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util";
+import { Role, Guild, Snowflake, Config, getRights, Member, Channel, DiscordApiErrors, handleFile, GuildCreateSchema } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { ChannelModifySchema } from "../channels/#channel_id";
const router: Router = Router();
-export interface GuildCreateSchema {
- /**
- * @maxLength 100
- */
- name: string;
- region?: string;
- icon?: string | null;
- channels?: ChannelModifySchema[];
- guild_template_code?: string;
- system_channel_id?: string;
- rules_channel_id?: string;
-}
-
//TODO: create default channel
router.post("/", route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }), async (req: Request, res: Response) => {
const body = req.body as GuildCreateSchema;
const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ id: req.user_id });
+ const guild_count = await Member.count({ where: { id: req.user_id } });
const rights = await getRights(req.user_id);
if ((guild_count >= maxGuilds)&&!rights.has("MANAGE_GUILDS")) {
throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
diff --git a/api/src/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 3d922e85..3a0de9e8 100644
--- a/api/src/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -1,15 +1,9 @@
import { Request, Response, Router } from "express";
-import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util";
+import { Template, Guild, Role, Snowflake, Config, User, Member, DiscordApiErrors, OrmUtils, GuildTemplateCreateSchema } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { DiscordApiErrors } from "@fosscord/util";
import fetch from "node-fetch";
const router: Router = Router();
-export interface GuildTemplateCreateSchema {
- name: string;
- avatar?: string | null;
-}
-
router.get("/:code", route({}), async (req: Request, res: Response) => {
const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
if (!enabled) res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403);
@@ -33,7 +27,7 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
return res.json(code.split("external:", 2)[1]);
}
- const template = await Template.findOneOrFail({ code: code });
+ const template = await Template.findOneOrFail({ where: { code } });
res.json(template);
});
@@ -47,23 +41,23 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req:
const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ id: req.user_id });
+ const guild_count = await Member.count({ where: { id: req.user_id } });
if (guild_count >= maxGuilds) {
throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
}
- const template = await Template.findOneOrFail({ code: code });
+ const template = await Template.findOneOrFail({ where: { code } });
const guild_id = Snowflake.generate();
const [guild, role] = await Promise.all([
- new Guild({
+ OrmUtils.mergeDeep(new Guild(), {
...body,
...template.serialized_source_guild,
id: guild_id,
owner_id: req.user_id
}).save(),
- new Role({
+ (OrmUtils.mergeDeep(new Role(), {
id: guild_id,
guild_id: guild_id,
color: 0,
@@ -74,7 +68,7 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req:
permissions: BigInt("2251804225"),
position: 0,
tags: null
- }).save()
+ }) as Role).save()
]);
await Member.addToGuild(req.user_id, guild_id);
diff --git a/api/src/routes/invites/index.ts b/src/api/routes/invites/index.ts
index 21da2d18..1b434505 100644
--- a/api/src/routes/invites/index.ts
+++ b/src/api/routes/invites/index.ts
@@ -1,7 +1,7 @@
import { Router, Request, Response } from "express";
import { emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, User, PublicInviteRelation } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
const router: Router = Router();
@@ -13,11 +13,11 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
res.status(200).send(invite);
});
-router.post("/:code", route({right: "JOIN_GUILDS"}), async (req: Request, res: Response) => {
+router.post("/:code", route({right: "USE_MASS_INVITES"}), async (req: Request, res: Response) => {
const { code } = req.params;
- const { guild_id } = await Invite.findOneOrFail({ code })
- const { features } = await Guild.findOneOrFail({ id: guild_id});
- const { public_flags } = await User.findOneOrFail({ id: req.user_id });
+ const { guild_id } = await Invite.findOneOrFail({ where: { code } })
+ const { features } = await Guild.findOneOrFail({ where: { id: guild_id} });
+ const { public_flags } = await User.findOneOrFail({ 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_CLOSED")) throw new HTTPError("Sorry, this guild has joins closed.", 403);
@@ -30,7 +30,7 @@ router.post("/:code", route({right: "JOIN_GUILDS"}), async (req: Request, res: R
// * cant use permission of route() function because path doesn't have guild_id/channel_id
router.delete("/:code", route({}), async (req: Request, res: Response) => {
const { code } = req.params;
- const invite = await Invite.findOneOrFail({ code });
+ const invite = await Invite.findOneOrFail({ where: { code } });
const { guild_id, channel_id } = invite;
const permission = await getPermission(req.user_id, guild_id, channel_id);
diff --git a/api/src/routes/oauth2/tokens.ts b/src/api/routes/oauth2/tokens.ts
index bd284221..bd284221 100644
--- a/api/src/routes/oauth2/tokens.ts
+++ b/src/api/routes/oauth2/tokens.ts
diff --git a/api/src/routes/outbound-promotions.ts b/src/api/routes/outbound-promotions.ts
index 411e95bf..411e95bf 100644
--- a/api/src/routes/outbound-promotions.ts
+++ b/src/api/routes/outbound-promotions.ts
diff --git a/api/src/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts
index 545c5c78..545c5c78 100644
--- a/api/src/routes/partners/#guild_id/requirements.ts
+++ b/src/api/routes/partners/#guild_id/requirements.ts
diff --git a/api/src/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 20cd07ba..20cd07ba 100644
--- a/api/src/routes/policies/instance/domains.ts
+++ b/src/api/routes/policies/instance/domains.ts
diff --git a/api/src/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index e3da014f..e3da014f 100644
--- a/api/src/routes/policies/instance/index.ts
+++ b/src/api/routes/policies/instance/index.ts
diff --git a/api/src/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 7de1476b..7de1476b 100644
--- a/api/src/routes/policies/instance/limits.ts
+++ b/src/api/routes/policies/instance/limits.ts
diff --git a/api/src/routes/scheduled-maintenances/upcoming_json.ts b/src/api/routes/scheduled-maintenances/upcoming_json.ts
index 83092e44..83092e44 100644
--- a/api/src/routes/scheduled-maintenances/upcoming_json.ts
+++ b/src/api/routes/scheduled-maintenances/upcoming_json.ts
diff --git a/api/src/routes/science.ts b/src/api/routes/science.ts
index 8556a3ad..8556a3ad 100644
--- a/api/src/routes/science.ts
+++ b/src/api/routes/science.ts
diff --git a/api/src/routes/stage-instances.ts b/src/api/routes/stage-instances.ts
index 411e95bf..411e95bf 100644
--- a/api/src/routes/stage-instances.ts
+++ b/src/api/routes/stage-instances.ts
diff --git a/api/src/routes/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts
index e6560d12..e6560d12 100644
--- a/api/src/routes/sticker-packs/index.ts
+++ b/src/api/routes/sticker-packs/index.ts
diff --git a/api/src/routes/stickers/#sticker_id/index.ts b/src/api/routes/stickers/#sticker_id/index.ts
index 293ca089..b484a7a1 100644
--- a/api/src/routes/stickers/#sticker_id/index.ts
+++ b/src/api/routes/stickers/#sticker_id/index.ts
@@ -6,7 +6,7 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { sticker_id } = req.params;
- res.json(await Sticker.find({ id: sticker_id }));
+ res.json(await Sticker.find({ where: { id: sticker_id } }));
});
export default router;
diff --git a/api/src/routes/stop.ts b/src/api/routes/stop.ts
index 7f8b78ba..7f8b78ba 100644
--- a/api/src/routes/stop.ts
+++ b/src/api/routes/stop.ts
diff --git a/api/src/routes/store/published-listings/applications.ts b/src/api/routes/store/published-listings/applications.ts
index 060a4c3d..060a4c3d 100644
--- a/api/src/routes/store/published-listings/applications.ts
+++ b/src/api/routes/store/published-listings/applications.ts
diff --git a/api/src/routes/store/published-listings/applications/#id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
index 54151ae5..54151ae5 100644
--- a/api/src/routes/store/published-listings/applications/#id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
diff --git a/api/src/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index 060a4c3d..060a4c3d 100644
--- a/api/src/routes/store/published-listings/skus.ts
+++ b/src/api/routes/store/published-listings/skus.ts
diff --git a/api/src/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
index 723a5160..e7f44ded 100644
--- a/api/src/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -9,23 +9,23 @@ const skus = new Map([
[
{
id: "511651856145973248",
- name: "Premium Monthly (Legacy)",
+ name: "Individual Premium Tier 2 Monthly (Legacy)",
interval: 1,
interval_count: 1,
tax_inclusive: true,
sku_id: "521842865731534868",
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
},
{
id: "511651860671627264",
- name: "Premium Yearly (Legacy)",
+ name: "Individiual Premium Tier 2 Yearly (Legacy)",
interval: 2,
interval_count: 1,
tax_inclusive: true,
sku_id: "521842865731534868",
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
}
@@ -36,23 +36,34 @@ const skus = new Map([
[
{
id: "511651871736201216",
- name: "Premium Classic Monthly",
+ name: "Individual Premium Tier 1 Monthly",
interval: 1,
interval_count: 1,
tax_inclusive: true,
sku_id: "521846918637420545",
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
},
{
id: "511651876987469824",
- name: "Premium Classic Yearly",
+ name: "Individual Premum Tier 1 Yearly",
interval: 2,
interval_count: 1,
tax_inclusive: true,
sku_id: "521846918637420545",
- currency: "usd",
+ currency: "eur",
+ price: 0,
+ price_tier: null
+ },
+ {
+ id: "978380684370378761",
+ name: "Individual Premum Tier 0",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
price: 0,
price_tier: null
}
@@ -63,34 +74,34 @@ const skus = new Map([
[
{
id: "642251038925127690",
- name: "Premium Quarterly",
+ name: "Individual Premium Tier 2 Quarterly",
interval: 1,
interval_count: 3,
tax_inclusive: true,
sku_id: "521847234246082599",
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
},
{
id: "511651880837840896",
- name: "Premium Monthly",
+ name: "Individual Premium Tier 2 Monthly",
interval: 1,
interval_count: 1,
tax_inclusive: true,
sku_id: "521847234246082599",
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
},
{
id: "511651885459963904",
- name: "Premium Yearly",
+ name: "Individual Premium Tier 2 Yearly",
interval: 2,
interval_count: 1,
tax_inclusive: true,
sku_id: "521847234246082599",
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
}
@@ -101,25 +112,25 @@ const skus = new Map([
[
{
id: "590665532894740483",
- name: "Server Boost Monthly",
+ name: "Crowd Premium Monthly",
interval: 1,
interval_count: 1,
tax_inclusive: true,
sku_id: "590663762298667008",
discount_price: 0,
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
},
{
id: "590665538238152709",
- name: "Server Boost Yearly",
+ name: "Crowd Premium Yearly",
interval: 2,
interval_count: 1,
tax_inclusive: true,
sku_id: "590663762298667008",
discount_price: 0,
- currency: "usd",
+ currency: "eur",
price: 0,
price_tier: null
}
diff --git a/api/src/routes/teams.ts b/src/api/routes/teams.ts
index 7ce3abcb..7ce3abcb 100644
--- a/api/src/routes/teams.ts
+++ b/src/api/routes/teams.ts
diff --git a/api/src/routes/template.ts.disabled b/src/api/routes/template.ts.disabled
index fcc59ef4..fcc59ef4 100644
--- a/api/src/routes/template.ts.disabled
+++ b/src/api/routes/template.ts.disabled
diff --git a/api/src/routes/track.ts b/src/api/routes/track.ts
index 8556a3ad..8556a3ad 100644
--- a/api/src/routes/track.ts
+++ b/src/api/routes/track.ts
diff --git a/api/src/routes/updates.ts b/src/api/routes/updates.ts
index cb4577c8..a24e94c1 100644
--- a/api/src/routes/updates.ts
+++ b/src/api/routes/updates.ts
@@ -7,7 +7,7 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { client } = Config.get();
- const release = await Release.findOneOrFail({ name: client.releases.upstreamVersion})
+ const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } })
res.json({
name: release.name,
diff --git a/api/src/routes/users/#id/index.ts b/src/api/routes/users/#id/index.ts
index bdb1060f..bdb1060f 100644
--- a/api/src/routes/users/#id/index.ts
+++ b/src/api/routes/users/#id/index.ts
diff --git a/api/src/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts
index 4dbb84cf..7a995a8c 100644
--- a/api/src/routes/users/#id/profile.ts
+++ b/src/api/routes/users/#id/profile.ts
@@ -15,10 +15,10 @@ router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }),
if (req.params.id === "@me") req.params.id = req.user_id;
const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
- var mutual_guilds: object[] = [];
- var premium_guild_since;
- const requested_member = await Member.find( { id: req.params.id, })
- const self_member = await Member.find( { id: req.user_id, })
+ let mutual_guilds: object[] = [];
+ let premium_guild_since;
+ const requested_member = await Member.find( { where: { id: req.params.id, } })
+ const self_member = await Member.find( { where: { id: req.user_id, } })
for(const rmem of requested_member) {
if(rmem.premium_since) {
diff --git a/api/src/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts
index de7cb9d3..61655c25 100644
--- a/api/src/routes/users/#id/relationships.ts
+++ b/src/api/routes/users/#id/relationships.ts
@@ -16,7 +16,7 @@ export interface UserRelationsResponse {
router.get("/", route({ test: { response: { body: "UserRelationsResponse" } } }), async (req: Request, res: Response) => {
- var mutual_relations: object[] = [];
+ let mutual_relations: object[] = [];
const requested_relations = await User.findOneOrFail({
where: { id: req.params.id },
relations: ["relationships"]
@@ -29,7 +29,7 @@ router.get("/", route({ test: { response: { body: "UserRelationsResponse" } } })
for(const rmem of requested_relations.relationships) {
for(const smem of self_relations.relationships)
if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) {
- var relation_user = await User.getPublicUser(rmem.to_id)
+ let relation_user = await User.getPublicUser(rmem.to_id)
mutual_relations.push({id: relation_user.id, username: relation_user.username, avatar: relation_user.avatar, discriminator: relation_user.discriminator, public_flags: relation_user.public_flags})
}
diff --git a/api/src/routes/users/@me/activities/statistics/applications.ts b/src/api/routes/users/@me/activities/statistics/applications.ts
index 014df8af..014df8af 100644
--- a/api/src/routes/users/@me/activities/statistics/applications.ts
+++ b/src/api/routes/users/@me/activities/statistics/applications.ts
diff --git a/api/src/routes/users/@me/affinities/guilds.ts b/src/api/routes/users/@me/affinities/guilds.ts
index 8d744744..8d744744 100644
--- a/api/src/routes/users/@me/affinities/guilds.ts
+++ b/src/api/routes/users/@me/affinities/guilds.ts
diff --git a/api/src/routes/users/@me/affinities/users.ts b/src/api/routes/users/@me/affinities/users.ts
index 6d4e4991..6d4e4991 100644
--- a/api/src/routes/users/@me/affinities/users.ts
+++ b/src/api/routes/users/@me/affinities/users.ts
diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/src/api/routes/users/@me/applications/#app_id/entitlements.ts
index 411e95bf..411e95bf 100644
--- a/api/src/routes/users/@me/applications/#app_id/entitlements.ts
+++ b/src/api/routes/users/@me/applications/#app_id/entitlements.ts
diff --git a/api/src/routes/users/@me/billing/country-code.ts b/src/api/routes/users/@me/billing/country-code.ts
index 33d40796..33d40796 100644
--- a/api/src/routes/users/@me/billing/country-code.ts
+++ b/src/api/routes/users/@me/billing/country-code.ts
diff --git a/api/src/routes/users/@me/billing/payment-sources.ts b/src/api/routes/users/@me/billing/payment-sources.ts
index 014df8af..014df8af 100644
--- a/api/src/routes/users/@me/billing/payment-sources.ts
+++ b/src/api/routes/users/@me/billing/payment-sources.ts
diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/src/api/routes/users/@me/billing/subscriptions.ts
index 411e95bf..411e95bf 100644
--- a/api/src/routes/users/@me/billing/subscriptions.ts
+++ b/src/api/routes/users/@me/billing/subscriptions.ts
diff --git a/api/src/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index 78f531e1..ad483529 100644
--- a/api/src/routes/users/@me/channels.ts
+++ b/src/api/routes/users/@me/channels.ts
@@ -1,5 +1,5 @@
import { Request, Response, Router } from "express";
-import { Recipient, DmChannelDTO, Channel } from "@fosscord/util";
+import { Recipient, DmChannelDTO, Channel, DmChannelCreateSchema } from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -12,11 +12,6 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id]))));
});
-export interface DmChannelCreateSchema {
- name?: string;
- recipients: string[];
-}
-
router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema;
res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
diff --git a/api/src/routes/users/@me/connections.ts b/src/api/routes/users/@me/connections.ts
index 411e95bf..411e95bf 100644
--- a/api/src/routes/users/@me/connections.ts
+++ b/src/api/routes/users/@me/connections.ts
diff --git a/api/src/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index c24c3f1e..1d81c2b9 100644
--- a/api/src/routes/users/@me/delete.ts
+++ b/src/api/routes/users/@me/delete.ts
@@ -2,7 +2,7 @@ import { Router, Request, Response } from "express";
import { Guild, Member, User } from "@fosscord/util";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
const router = Router();
diff --git a/api/src/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts
index 8556a3ad..8556a3ad 100644
--- a/api/src/routes/users/@me/devices.ts
+++ b/src/api/routes/users/@me/devices.ts
diff --git a/api/src/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 4aff3774..4aff3774 100644
--- a/api/src/routes/users/@me/disable.ts
+++ b/src/api/routes/users/@me/disable.ts
diff --git a/api/src/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index 3114984e..3114984e 100644
--- a/api/src/routes/users/@me/email-settings.ts
+++ b/src/api/routes/users/@me/email-settings.ts
diff --git a/api/src/routes/users/@me/entitlements.ts b/src/api/routes/users/@me/entitlements.ts
index 341e2b4c..341e2b4c 100644
--- a/api/src/routes/users/@me/entitlements.ts
+++ b/src/api/routes/users/@me/entitlements.ts
diff --git a/api/src/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index 754a240e..4d4fccd4 100644
--- a/api/src/routes/users/@me/guilds.ts
+++ b/src/api/routes/users/@me/guilds.ts
@@ -1,6 +1,6 @@
import { Router, Request, Response } from "express";
import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent, Config } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
diff --git a/api/src/routes/users/@me/guilds/premium/subscription-slots.ts b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
index 014df8af..014df8af 100644
--- a/api/src/routes/users/@me/guilds/premium/subscription-slots.ts
+++ b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
diff --git a/api/src/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index 1af413c4..7d095451 100644
--- a/api/src/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -1,39 +1,22 @@
import { Router, Request, Response } from "express";
-import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors } from "@fosscord/util";
+import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors, UserModifySchema } from "@fosscord/util";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
+import { OrmUtils, generateToken } from "@fosscord/util";
const router: Router = Router();
-export interface UserModifySchema {
- /**
- * @minLength 1
- * @maxLength 100
- */
- username?: string;
- avatar?: string | null;
- /**
- * @maxLength 1024
- */
- bio?: string;
- accent_color?: number;
- banner?: string | null;
- password?: string;
- new_password?: string;
- code?: string;
-}
-
router.get("/", route({}), async (req: Request, res: Response) => {
res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } }));
});
router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => {
+ var token = null as any;
const body = req.body as UserModifySchema;
if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
+ let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
if (body.password) {
if (user.data?.hash) {
@@ -53,10 +36,12 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
});
}
user.data.hash = await bcrypt.hash(body.new_password, 12);
+ user.data.valid_tokens_since = new Date();
+ token = await generateToken(user.id) as string;
}
if(body.username){
- var check_username = body?.username?.replace(/\s/g, '');
+ let check_username = body?.username?.replace(/\s/g, '');
if(!check_username) {
throw FieldErrors({
username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
@@ -64,7 +49,7 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
}
}
- user.assign(body);
+ user = OrmUtils.mergeDeep(user, body);
await user.save();
// @ts-ignore
@@ -76,8 +61,11 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
user_id: req.user_id,
data: user
} as UserUpdateEvent);
-
- res.json(user);
+
+ res.json({
+ ...user,
+ token
+ });
});
export default router;
diff --git a/api/src/routes/users/@me/library.ts b/src/api/routes/users/@me/library.ts
index 7ac13bae..7ac13bae 100644
--- a/api/src/routes/users/@me/library.ts
+++ b/src/api/routes/users/@me/library.ts
diff --git a/api/src/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index 0c13cdba..f7464b99 100644
--- a/api/src/routes/users/@me/relationships.ts
+++ b/src/api/routes/users/@me/relationships.ts
@@ -9,9 +9,10 @@ import {
Config
} from "@fosscord/util";
import { Router, Response, Request } from "express";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import { DiscordApiErrors } from "@fosscord/util";
import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
const router = Router();
@@ -37,24 +38,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.json(related_users);
});
-export interface RelationshipPutSchema {
- type?: RelationshipType;
-}
-
router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => {
return await updateRelationship(
req,
res,
- await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }),
+ await User.findOneOrFail({ where: { id: req.params.id }, relations: ["relationships", "relationships.to"], select: userProjection }),
req.body.type ?? RelationshipType.friends
);
});
-export interface RelationshipPostSchema {
- discriminator: string;
- username: string;
-}
-
router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => {
return await updateRelationship(
req,
@@ -75,8 +67,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
- const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] });
- const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] });
+ const user = await User.findOneOrFail({ where: { id: req.user_id }, select: userProjection, relations: ["relationships"] });
+ const friend = await User.findOneOrFail({ where: { id: id }, select: userProjection, relations: ["relationships"] });
const relationship = user.relationships.find((x) => x.to_id === id);
const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
@@ -124,12 +116,13 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
const id = friend.id;
if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
- const user = await User.findOneOrFail(
- { id: req.user_id },
- { relations: ["relationships", "relationships.to"], select: userProjection }
- );
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection
+ });
- var relationship = user.relationships.find((x) => x.to_id === id);
+ let relationship = user.relationships.find((x) => x.to_id === id);
const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
// TODO: you can add infinitely many blocked users (should this be prevented?)
@@ -139,7 +132,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
relationship.type = RelationshipType.blocked;
await relationship.save();
} else {
- relationship = await new Relationship({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save();
+ relationship = await (OrmUtils.mergeDeep(new Relationship(), { to_id: id, type: RelationshipType.blocked, from_id: req.user_id }) as Relationship).save();
}
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
@@ -165,8 +158,8 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
const { maxFriends } = Config.get().limits.user;
if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
- var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
- var outgoing_relationship = new Relationship({
+ let incoming_relationship = OrmUtils.mergeDeep(new Relationship(), { nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
+ let outgoing_relationship = OrmUtils.mergeDeep(new Relationship(), {
nickname: undefined,
type: RelationshipType.outgoing,
to: friend,
@@ -177,7 +170,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
// accept friend request
- incoming_relationship = friendRequest;
+ incoming_relationship = friendRequest as any; //TODO: checkme, any cast
incoming_relationship.type = RelationshipType.friends;
}
@@ -185,7 +178,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
- outgoing_relationship = relationship;
+ outgoing_relationship = relationship as any; //TODO: checkme, any cast
outgoing_relationship.type = RelationshipType.friends;
}
diff --git a/api/src/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index b22b72fb..7578d36e 100644
--- a/api/src/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -4,14 +4,12 @@ import { route } from "@fosscord/api";
const router = Router();
-export interface UserSettingsSchema extends Partial<UserSettings> {}
-
router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => {
const body = req.body as UserSettings;
if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale
- const user = await User.findOneOrFail({ id: req.user_id, bot: false });
- user.settings = { ...user.settings, ...body };
+ const user = await User.findOneOrFail({ where: { id: req.user_id, bot: false }, relations: ["settings"] });
+ user.settings = { ...user.settings, ...body } as UserSettings;
await user.save();
res.sendStatus(204);
diff --git a/api/src/routes/voice/regions.ts b/src/api/routes/voice/regions.ts
index 4de304ee..4de304ee 100644
--- a/api/src/routes/voice/regions.ts
+++ b/src/api/routes/voice/regions.ts
diff --git a/api/src/start.ts b/src/api/start.ts
index ccb4d108..9ba198e7 100644
--- a/api/src/start.ts
+++ b/src/api/start.ts
@@ -1,13 +1,12 @@
process.on("uncaughtException", console.error);
process.on("unhandledRejection", console.error);
-import "missing-native-js-functions";
import { config } from "dotenv";
config();
import { FosscordServer } from "./Server";
import cluster from "cluster";
import os from "os";
-var cores = 1;
+let cores = 1;
try {
cores = Number(process.env.THREADS) || os.cpus().length;
} catch {
@@ -27,7 +26,7 @@ if (cluster.isMaster && process.env.NODE_ENV == "production") {
cluster.fork();
});
} else {
- var port = Number(process.env.PORT) || 3001;
+ let port = Number(process.env.PORT) || 3001;
const server = new FosscordServer({ port });
server.start().catch(console.error);
diff --git a/api/src/util/entities/blockedEmailDomains.txt b/src/api/util/entities/blockedEmailDomains.txt
index eb88305d..eb88305d 100644
--- a/api/src/util/entities/blockedEmailDomains.txt
+++ b/src/api/util/entities/blockedEmailDomains.txt
diff --git a/api/src/util/entities/trustedEmailDomains.txt b/src/api/util/entities/trustedEmailDomains.txt
index 38ffa4fa..38ffa4fa 100644
--- a/api/src/util/entities/trustedEmailDomains.txt
+++ b/src/api/util/entities/trustedEmailDomains.txt
diff --git a/api/src/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts
index 6bddfa98..7c337270 100644
--- a/api/src/util/handlers/Instance.ts
+++ b/src/api/util/handlers/Instance.ts
@@ -1,4 +1,5 @@
import { Config, Guild, Session } from "@fosscord/util";
+import { createQueryBuilder } from "typeorm";
export async function initInstance() {
// TODO: clean up database and delete tombstone data
@@ -9,7 +10,7 @@ export async function initInstance() {
const { autoJoin } = Config.get().guild;
if (autoJoin.enabled && !autoJoin.guilds?.length) {
- let guild = await Guild.findOne({});
+ let guild = await Guild.findOne({where: {}, order: {id: "ASC"}});
if (guild) {
// @ts-ignore
await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
diff --git a/api/src/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index e9f0ac55..ff5ece75 100644
--- a/api/src/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -21,11 +21,13 @@ import {
Webhook,
Attachment,
Config,
+ MessageCreateSchema,
} from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { HTTPError } from "@fosscord/util";
import fetch from "node-fetch";
import cheerio from "cheerio";
-import { MessageCreateSchema } from "../../routes/channels/#channel_id/messages";
+import { OrmUtils } from "@fosscord/util";
+
const allow_empty = false;
// TODO: check webhook, application, system author, stickers
// TODO: embed gifs/videos/images
@@ -38,7 +40,7 @@ const DEFAULT_FETCH_OPTIONS: any = {
headers: {
"user-agent": "Mozilla/5.0 (compatible; Fosscord/1.0; +https://github.com/fosscord/fosscord)"
},
- size: 1024 * 1024 * 1,
+ // size: 1024 * 1024 * 5, // grabbed from config later
compress: true,
method: "GET"
};
@@ -47,7 +49,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] });
if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
- const message = new Message({
+ const message = OrmUtils.mergeDeep(new Message(), {
...opts,
sticker_items: opts.sticker_ids?.map((x) => ({ id: x })),
guild_id: channel.guild_id,
@@ -68,10 +70,10 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
rights.hasThrow("SEND_MESSAGES");
}
if (opts.application_id) {
- message.application = await Application.findOneOrFail({ id: opts.application_id });
+ message.application = await Application.findOneOrFail({ where: { id: opts.application_id } });
}
if (opts.webhook_id) {
- message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id });
+ message.webhook = await Webhook.findOneOrFail({ where: { id: opts.webhook_id } });
}
const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id);
@@ -85,7 +87,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
permission.hasThrow("READ_MESSAGE_HISTORY");
// code below has to be redone when we add custom message routing
if (message.guild_id !== null) {
- const guild = await Guild.findOneOrFail({ id: channel.guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } });
if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
@@ -102,11 +104,11 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
throw new HTTPError("Empty messages are not allowed", 50006);
}
- var content = opts.content;
- var mention_channel_ids = [] as string[];
- var mention_role_ids = [] as string[];
- var mention_user_ids = [] as string[];
- var mention_everyone = false;
+ let content = opts.content;
+ let mention_channel_ids = [] as string[];
+ let mention_role_ids = [] as string[];
+ let mention_user_ids = [] as string[];
+ let mention_everyone = false;
if (content) { // TODO: explicit-only mentions
message.content = content.trim();
@@ -120,7 +122,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
await Promise.all(
Array.from(content.matchAll(ROLE_MENTION)).map(async ([_, mention]) => {
- const role = await Role.findOneOrFail({ id: mention, guild_id: channel.guild_id });
+ const role = await Role.findOneOrFail({ where: { id: mention, guild_id: channel.guild_id } });
if (role.mentionable || permission.has("MANAGE_ROLES")) {
mention_role_ids.push(mention);
}
@@ -132,9 +134,9 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
}
}
- message.mention_channels = mention_channel_ids.map((x) => new Channel({ id: x }));
- message.mention_roles = mention_role_ids.map((x) => new Role({ id: x }));
- message.mentions = mention_user_ids.map((x) => new User({ id: x }));
+ message.mention_channels = mention_channel_ids.map((x) => OrmUtils.mergeDeep(new Channel(), { id: x }));
+ message.mention_roles = mention_role_ids.map((x) => OrmUtils.mergeDeep(new Role(), { id: x }));
+ message.mentions = mention_user_ids.map((x) => OrmUtils.mergeDeep(new User(), { id: x }));
message.mention_everyone = mention_everyone;
// TODO: check and put it all in the body
@@ -144,7 +146,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
// TODO: cache link result in db
export async function postHandleMessage(message: Message) {
- var links = message.content?.match(LINK_REGEX);
+ let links = message.content?.match(LINK_REGEX);
if (!links) return;
const data = { ...message };
@@ -154,7 +156,10 @@ export async function postHandleMessage(message: Message) {
for (const link of links) {
try {
- const request = await fetch(link, DEFAULT_FETCH_OPTIONS);
+ const request = await fetch(link, {
+ ...DEFAULT_FETCH_OPTIONS,
+ size: Config.get().limits.message.maxEmbedDownloadSize,
+ });
const text = await request.text();
const $ = cheerio.load(text);
@@ -191,16 +196,17 @@ export async function postHandleMessage(message: Message) {
channel_id: message.channel_id,
data
} as MessageUpdateEvent),
- Message.update({ id: message.id, channel_id: message.channel_id }, data)
+ Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })
]);
}
export async function sendMessage(opts: MessageOptions) {
const message = await handleMessage({ ...opts, timestamp: new Date() });
+ //TODO: check this, removed toJSON call
await Promise.all([
Message.insert(message),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent)
+ emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message } as MessageCreateEvent)
]);
postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly
diff --git a/api/src/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index 4d60eb91..4d60eb91 100644
--- a/api/src/util/handlers/Voice.ts
+++ b/src/api/util/handlers/Voice.ts
diff --git a/api/src/util/handlers/route.ts b/src/api/util/handlers/route.ts
index 3d3bbc37..71e14955 100644
--- a/api/src/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -19,7 +19,7 @@ import Ajv from "ajv";
import { AnyValidateFunction } from "ajv/dist/core";
import addFormats from "ajv-formats";
-const SchemaPath = path.join(__dirname, "..", "..", "..", "assets", "schemas.json");
+const SchemaPath = path.join(__dirname, "..", "..", "..","..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
export const ajv = new Ajv({
@@ -87,7 +87,7 @@ const normalizeBody = (body: any = {}) => {
};
export function route(opts: RouteOptions) {
- var validate: AnyValidateFunction<any> | undefined;
+ let validate: AnyValidateFunction<any> | undefined;
if (opts.body) {
validate = ajv.getSchema(opts.body);
if (!validate) throw new Error(`Body schema ${opts.body} not found`);
@@ -117,6 +117,11 @@ export function route(opts: RouteOptions) {
const valid = validate(normalizeBody(req.body));
if (!valid) {
const fields: Record<string, { code?: string; message: string }> = {};
+ if(process.env.LOG_INVALID_BODY) {
+ console.log(`Got invalid request: ${req.method} ${req.originalUrl}`)
+ console.log(req.body)
+ validate.errors?.forEach(x => console.log(x.params))
+ }
validate.errors?.forEach((x) => (fields[x.instancePath.slice(1)] = { code: x.keyword, message: x.message || "" }));
throw FieldErrors(fields);
}
diff --git a/api/src/util/index.ts b/src/api/util/index.ts
index ffbcf24e..b3c7559f 100644
--- a/api/src/util/index.ts
+++ b/src/api/util/index.ts
@@ -1,8 +1,9 @@
+export * from "./entities/AssetCacheItem";
+export * from "./handlers/Message";
+export * from "./handlers/route";
+export * from "./handlers/Voice";
export * from "./utility/Base64";
export * from "./utility/ipAddress";
-export * from "./handlers/Message";
export * from "./utility/passwordStrength";
export * from "./utility/RandomInviteID";
-export * from "./handlers/route";
-export * from "./utility/String";
-export * from "./handlers/Voice";
+export * from "./utility/String";
\ No newline at end of file
diff --git a/api/src/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 46cff77a..46cff77a 100644
--- a/api/src/util/utility/Base64.ts
+++ b/src/api/util/utility/Base64.ts
diff --git a/api/src/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
index 7ea344e0..7ea344e0 100644
--- a/api/src/util/utility/RandomInviteID.ts
+++ b/src/api/util/utility/RandomInviteID.ts
diff --git a/api/src/util/utility/String.ts b/src/api/util/utility/String.ts
index 982b7e11..982b7e11 100644
--- a/api/src/util/utility/String.ts
+++ b/src/api/util/utility/String.ts
diff --git a/api/src/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
index 13cc9603..8d986b26 100644
--- a/api/src/util/utility/ipAddress.ts
+++ b/src/api/util/utility/ipAddress.ts
@@ -65,7 +65,7 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> {
const { ipdataApiKey } = Config.get().security;
if (!ipdataApiKey) return { ...exampleData, ip };
- return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json();
+ return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json() as any;
}
export function isProxy(data: typeof exampleData) {
diff --git a/api/src/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts
index 439700d0..8eca63b8 100644
--- a/api/src/util/utility/passwordStrength.ts
+++ b/src/api/util/utility/passwordStrength.ts
@@ -1,5 +1,4 @@
import { Config } from "@fosscord/util";
-import "missing-native-js-functions";
const reNUMBER = /[0-9]/g;
const reUPPERCASELETTER = /[A-Z]/g;
@@ -19,7 +18,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
*/
export function checkPassword(password: string): number {
const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
- var strength = 0;
+ let strength = 0;
// checks for total password len
if (password.length >= minLength - 1) {
|