summary refs log tree commit diff
path: root/api/src/routes/guilds
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-08-30 15:05:23 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-08-30 15:08:18 +1000
commit16315a3170ec018a834e68360e06b506415446d2 (patch)
tree90cfe456040fce35b904e88462886e3c73a2f3f2 /api/src/routes/guilds
parentStart listening after database and config has been loaded (diff)
parentOop, deprecated typeorm call (diff)
downloadserver-16315a3170ec018a834e68360e06b506415446d2.tar.xz
Merge branch 'staging' into dev/Maddy/fix/listeningAfterDb
Diffstat (limited to 'api/src/routes/guilds')
-rw-r--r--api/src/routes/guilds/#guild_id/audit-logs.ts20
-rw-r--r--api/src/routes/guilds/#guild_id/bans.ts178
-rw-r--r--api/src/routes/guilds/#guild_id/channels.ts60
-rw-r--r--api/src/routes/guilds/#guild_id/delete.ts30
-rw-r--r--api/src/routes/guilds/#guild_id/discovery-requirements.ts39
-rw-r--r--api/src/routes/guilds/#guild_id/emojis.ts118
-rw-r--r--api/src/routes/guilds/#guild_id/index.ts75
-rw-r--r--api/src/routes/guilds/#guild_id/integrations.ts12
-rw-r--r--api/src/routes/guilds/#guild_id/invites.ts15
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/index.ts101
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/nick.ts26
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts21
-rw-r--r--api/src/routes/guilds/#guild_id/members/index.ts31
-rw-r--r--api/src/routes/guilds/#guild_id/premium.ts10
-rw-r--r--api/src/routes/guilds/#guild_id/prune.ts86
-rw-r--r--api/src/routes/guilds/#guild_id/regions.ts15
-rw-r--r--api/src/routes/guilds/#guild_id/roles/#role_id/index.ts68
-rw-r--r--api/src/routes/guilds/#guild_id/roles/index.ts111
-rw-r--r--api/src/routes/guilds/#guild_id/stickers.ts135
-rw-r--r--api/src/routes/guilds/#guild_id/templates.ts92
-rw-r--r--api/src/routes/guilds/#guild_id/vanity-url.ts66
-rw-r--r--api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts60
-rw-r--r--api/src/routes/guilds/#guild_id/webhooks.ts12
-rw-r--r--api/src/routes/guilds/#guild_id/welcome_screen.ts42
-rw-r--r--api/src/routes/guilds/#guild_id/widget.json.ts82
-rw-r--r--api/src/routes/guilds/#guild_id/widget.png.ts111
-rw-r--r--api/src/routes/guilds/#guild_id/widget.ts32
-rw-r--r--api/src/routes/guilds/index.ts46
-rw-r--r--api/src/routes/guilds/templates/index.ts85
29 files changed, 0 insertions, 1779 deletions
diff --git a/api/src/routes/guilds/#guild_id/audit-logs.ts b/api/src/routes/guilds/#guild_id/audit-logs.ts
deleted file mode 100644
index a4f2f800..00000000
--- a/api/src/routes/guilds/#guild_id/audit-logs.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-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
-router.get("/", route({}), async (req: Request, res: Response) => {
-	res.json({
-		audit_log_entries: [],
-		users: [],
-		integrations: [],
-		webhooks: [],
-		guild_scheduled_events: [],
-		threads: [],
-		application_commands: []
-	});
-});
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts
deleted file mode 100644
index 1ce41936..00000000
--- a/api/src/routes/guilds/#guild_id/bans.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-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 { 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;
-};
-
-const router: Router = Router();
-
-/* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */
-
-router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	let bans = await Ban.find({ guild_id: guild_id });
-	let promisesToAwait: object[] = [];
-	const bansObj: object[] = [];
-
-	bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
-
-	bans.forEach((ban) => {
-		promisesToAwait.push(User.getPublicUser(ban.user_id));
-	});
-
-	const bannedUsers: object[] = await Promise.all(promisesToAwait);
-
-	bans.forEach((ban, index) => {
-		const user = bannedUsers[index] as User;
-		bansObj.push({
-			reason: ban.reason,
-			user: {
-				username: user.username,
-				discriminator: user.discriminator,
-				id: user.id,
-				avatar: user.avatar,
-				public_flags: user.public_flags
-			}
-		});
-	});
-
-	return res.json(bansObj);
-});
-
-router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
-	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;
-	
-	if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
-	// pretend self-bans don't exist to prevent victim chasing
-	
-	/* Filter secret from registry. */
-	
-	ban = ban as BanModeratorSchema;
-
-	delete ban.ip
-
-	return res.json(ban);
-});
-
-router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const banned_user_id = req.params.user_id;
-
-	if ( (req.user_id === banned_user_id) && (banned_user_id === req.permission!.cache.guild?.owner_id))
-		throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
-	
-	if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400);
-	
-	const banned_user = await User.getPublicUser(banned_user_id);
-
-	const ban = new Ban({
-		user_id: banned_user_id,
-		guild_id: guild_id,
-		ip: getIpAdress(req),
-		executor_id: req.user_id,
-		reason: req.body.reason // || otherwise empty
-	});
-
-	await Promise.all([
-		Member.removeFromGuild(banned_user_id, guild_id),
-		ban.save(),
-		emitEvent({
-			event: "GUILD_BAN_ADD",
-			data: {
-				guild_id: guild_id,
-				user: banned_user
-			},
-			guild_id: guild_id
-		} as GuildBanAddEvent)
-	]);
-
-	return res.json(ban);
-});
-
-router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	const banned_user = await User.getPublicUser(req.params.user_id);
-
-	if (req.permission!.cache.guild?.owner_id === req.params.user_id) 
-		throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
-	
-	const ban = new Ban({
-		user_id: req.params.user_id,
-		guild_id: guild_id,
-		ip: getIpAdress(req),
-		executor_id: req.params.user_id,
-		reason: req.body.reason // || otherwise empty
-	});
-
-	await Promise.all([
-		Member.removeFromGuild(req.user_id, guild_id),
-		ban.save(),
-		emitEvent({
-			event: "GUILD_BAN_ADD",
-			data: {
-				guild_id: guild_id,
-				user: banned_user
-			},
-			guild_id: guild_id
-		} as GuildBanAddEvent)
-	]);
-
-	return res.json(ban);
-});
-
-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 });
-	
-	if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
-	// make self-bans irreversible and hide them from view to avoid victim chasing
-	
-	const banned_user = await User.getPublicUser(user_id);
-	
-	await Promise.all([
-		Ban.delete({
-			user_id: user_id,
-			guild_id
-		}),
-
-		emitEvent({
-			event: "GUILD_BAN_REMOVE",
-			data: {
-				guild_id,
-				user: banned_user
-			},
-			guild_id
-		} as GuildBanRemoveEvent)
-	]);
-
-	return res.status(204).send();
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts
deleted file mode 100644
index a921fa21..00000000
--- a/api/src/routes/guilds/#guild_id/channels.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-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();
-
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const channels = await Channel.find({ guild_id });
-
-	res.json(channels);
-});
-
-router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
-	// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
-	const { guild_id } = req.params;
-	const body = req.body as ChannelModifySchema;
-
-	const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
-
-	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;
-	const body = req.body as ChannelReorderSchema;
-
-	await Promise.all([
-		body.map(async (x) => {
-			if (x.position == null && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400);
-
-			const opts: any = {};
-			if (x.position != null) opts.position = x.position;
-
-			if (x.parent_id) {
-				opts.parent_id = x.parent_id;
-				const parent_channel = await Channel.findOneOrFail({
-					where: { id: x.parent_id, guild_id },
-					select: ["permission_overwrites"]
-				});
-				if (x.lock_permissions) {
-					opts.permission_overwrites = parent_channel.permission_overwrites;
-				}
-			}
-
-			await Channel.update({ guild_id, id: x.id }, opts);
-			const channel = await Channel.findOneOrFail({ guild_id, id: x.id });
-
-			await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent);
-		})
-	]);
-
-	res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts
deleted file mode 100644
index bd158c56..00000000
--- a/api/src/routes/guilds/#guild_id/delete.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util";
-import { Router, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
-
-const router = Router();
-
-// discord prefixes this route with /delete instead of using the delete method
-// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
-router.post("/", route({}), async (req: Request, res: Response) => {
-	var { 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);
-
-	await Promise.all([
-		Guild.delete({ id: guild_id }), // this will also delete all guild related data
-		emitEvent({
-			event: "GUILD_DELETE",
-			data: {
-				id: guild_id
-			},
-			guild_id: guild_id
-		} as GuildDeleteEvent)
-	]);
-
-	return res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/discovery-requirements.ts b/api/src/routes/guilds/#guild_id/discovery-requirements.ts
deleted file mode 100644
index ad20633f..00000000
--- a/api/src/routes/guilds/#guild_id/discovery-requirements.ts
+++ /dev/null
@@ -1,39 +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 { guild_id } = req.params;	
-    // TODO:
-    // Load from database
-    // Admin control, but for now it allows anyone to be discoverable
-
-	res.send({
-		guild_id: guild_id,
-		safe_environment: true,
-        healthy: true,
-        health_score_pending: false,
-        size: true,
-        nsfw_properties: {},
-        protected: true,
-        sufficient: true,
-        sufficient_without_grace_period: true,
-        valid_rules_channel: true,
-        retention_healthy: true,
-        engagement_healthy: true,
-        age: true,
-        minimum_age: 0,
-        health_score: {
-            avg_nonnew_participators: 0,
-            avg_nonnew_communicators: 0,
-            num_intentful_joiners: 0,
-            perc_ret_w1_intentful: 0
-        },
-        minimum_size: 0
-	});
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/emojis.ts b/api/src/routes/guilds/#guild_id/emojis.ts
deleted file mode 100644
index 85d7ac05..00000000
--- a/api/src/routes/guilds/#guild_id/emojis.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Router, Request, Response } from "express";
-import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User } from "@fosscord/util";
-import { route } from "@fosscord/api";
-
-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;
-
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-
-	const emojis = await Emoji.find({ where: { guild_id: guild_id }, relations: ["user"] });
-
-	return res.json(emojis);
-});
-
-router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
-	const { guild_id, emoji_id } = req.params;
-
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-
-	const emoji = await Emoji.findOneOrFail({ where: { guild_id: guild_id, id: emoji_id }, relations: ["user"] });
-
-	return res.json(emoji);
-});
-
-router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const body = req.body as EmojiCreateSchema;
-
-	const id = Snowflake.generate();
-	const emoji_count = await Emoji.count({ guild_id: guild_id });
-	const { maxEmojis } = Config.get().limits.guild;
-
-	if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
-	if (body.require_colons == null) body.require_colons = true;
-
-	const user = await User.findOneOrFail({ id: req.user_id });
-	body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
-
-	const emoji = await new Emoji({
-		id: id,
-		guild_id: guild_id,
-		...body,
-		user: user,
-		managed: false,
-		animated: false, // TODO: Add support animated emojis
-		available: true,
-		roles: []
-	}).save();
-
-	await emitEvent({
-		event: "GUILD_EMOJIS_UPDATE",
-		guild_id: guild_id,
-		data: {
-			guild_id: guild_id,
-			emojis: await Emoji.find({ guild_id: guild_id })
-		}
-	} as GuildEmojisUpdateEvent);
-
-	return res.status(201).json(emoji);
-});
-
-router.patch(
-	"/:emoji_id",
-	route({ body: "EmojiModifySchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }),
-	async (req: Request, res: Response) => {
-		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();
-
-		await emitEvent({
-			event: "GUILD_EMOJIS_UPDATE",
-			guild_id: guild_id,
-			data: {
-				guild_id: guild_id,
-				emojis: await Emoji.find({ guild_id: guild_id })
-			}
-		} as GuildEmojisUpdateEvent);
-
-		return res.json(emoji);
-	}
-);
-
-router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
-	const { emoji_id, guild_id } = req.params;
-
-	await Emoji.delete({
-		id: emoji_id,
-		guild_id: guild_id
-	});
-
-	await emitEvent({
-		event: "GUILD_EMOJIS_UPDATE",
-		guild_id: guild_id,
-		data: {
-			guild_id: guild_id,
-			emojis: await Emoji.find({ guild_id: guild_id })
-		}
-	} as GuildEmojisUpdateEvent);
-
-	res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts
deleted file mode 100644
index 4ec3df72..00000000
--- a/api/src/routes/guilds/#guild_id/index.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
-import "missing-native-js-functions";
-import { GuildCreateSchema } from "../index";
-
-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 })
-	]);
-	if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
-
-	// @ts-ignore
-	guild.joined_at = member?.joined_at;
-
-	return res.send(guild);
-});
-
-router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res: Response) => {
-	const body = req.body as GuildUpdateSchema;
-	const { guild_id } = req.params;
-	
-	
-	const rights = await getRights(req.user_id);
-	const permission = await getPermission(req.user_id, guild_id);
-	
-	if (!rights.has("MANAGE_GUILDS")||!permission.has("MANAGE_GUILD"))
-		throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD");
-	
-	// TODO: guild update check image
-
-	if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
-	if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
-	if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
-
-	var guild = await Guild.findOneOrFail({
-		where: { id: guild_id },
-		relations: ["emojis", "roles", "stickers"]
-	});
-	// TODO: check if body ids are valid
-	guild.assign(body);
-
-	const data = guild.toJSON();
-	// TODO: guild hashes
-	// TODO: fix vanity_url_code, template_id
-	delete data.vanity_url_code;
-	delete data.template_id;
-
-	await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data, guild_id } as GuildUpdateEvent)]);
-
-	return res.json(data);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/integrations.ts b/api/src/routes/guilds/#guild_id/integrations.ts
deleted file mode 100644
index abf997c9..00000000
--- a/api/src/routes/guilds/#guild_id/integrations.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-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 integrations list
-router.get("/", route({}), async (req: Request, res: Response) => {
-	res.json([]);
-});
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts
deleted file mode 100644
index b7534e31..00000000
--- a/api/src/routes/guilds/#guild_id/invites.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { getPermission, Invite, PublicInviteRelation } from "@fosscord/util";
-import { route } from "@fosscord/api";
-import { Request, Response, Router } from "express";
-
-const router = Router();
-
-router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation });
-
-	return res.json(invites);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
deleted file mode 100644
index c285abb3..00000000
--- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Rights, Guild } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
-
-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 });
-
-	return res.json(member);
-});
-
-router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => {
-	let { guild_id, member_id } = req.params;
-	if (member_id === "@me") member_id = req.user_id;
-	const body = req.body as MemberChangeSchema;
-
-	const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] });
-	const permission = await getPermission(req.user_id, guild_id);
-	const everyone = await Role.findOneOrFail({ 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
-	}
-
-	await member.save();
-
-	member.roles = member.roles.filter((x) => x.id !== everyone.id);
-
-	// do not use promise.all as we have to first write to db before emitting the event to catch errors
-	await emitEvent({
-		event: "GUILD_MEMBER_UPDATE",
-		guild_id,
-		data: { ...member, roles: member.roles.map((x) => x.id) }
-	} as GuildMemberUpdateEvent);
-
-	res.json(member);
-});
-
-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;
-		rights.hasThrow("JOIN_GUILDS");
-	} else {
-		// TODO: join others by controller	
-	}
-
-	var guild = await Guild.findOneOrFail({
-		where: { id: guild_id }
-	});
-
-	var emoji = await Emoji.find({
-		where: { guild_id: guild_id }
-	});
-
-	var roles = await Role.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 });
-});
-
-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);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts
deleted file mode 100644
index 27f7f65d..00000000
--- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { getPermission, Member, PermissionResolvable } from "@fosscord/util";
-import { route } from "@fosscord/api";
-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";
-	if (member_id === "@me") {
-		member_id = req.user_id;
-		permissionString = "CHANGE_NICKNAME";
-	}
-
-	const perms = await getPermission(req.user_id, guild_id);
-	perms.hasThrow(permissionString);
-
-	await Member.changeNickname(member_id, guild_id, req.body.nick);
-	res.status(200).send();
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
deleted file mode 100644
index 8f5ca7ba..00000000
--- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { getPermission, Member } from "@fosscord/util";
-import { route } from "@fosscord/api";
-import { Request, Response, Router } from "express";
-
-const router = Router();
-
-router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
-	const { guild_id, role_id, member_id } = req.params;
-
-	await Member.removeRole(member_id, guild_id, role_id);
-	res.sendStatus(204);
-});
-
-router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
-	const { guild_id, role_id, member_id } = req.params;
-
-	await Member.addRole(member_id, guild_id, role_id);
-	res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts
deleted file mode 100644
index b730a4e7..00000000
--- a/api/src/routes/guilds/#guild_id/members/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-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";
-
-const router = Router();
-
-// TODO: send over websocket
-// TODO: check for GUILD_MEMBERS intent
-
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const limit = Number(req.query.limit) || 1;
-	if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000");
-	const after = `${req.query.after}`;
-	const query = after ? { id: MoreThan(after) } : {};
-
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-
-	const members = await Member.find({
-		where: { guild_id, ...query },
-		select: PublicMemberProjection,
-		take: limit,
-		order: { id: "ASC" }
-	});
-
-	return res.json(members);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/premium.ts b/api/src/routes/guilds/#guild_id/premium.ts
deleted file mode 100644
index 75361ac6..00000000
--- a/api/src/routes/guilds/#guild_id/premium.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Router, Request, Response } from "express";
-import { route } from "@fosscord/api";
-const router = Router();
-
-router.get("/subscriptions", route({}), async (req: Request, res: Response) => {
-	// TODO:
-	res.json([]);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/prune.ts b/api/src/routes/guilds/#guild_id/prune.ts
deleted file mode 100644
index 0e587d22..00000000
--- a/api/src/routes/guilds/#guild_id/prune.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Router, Request, Response } from "express";
-import { Guild, Member, Snowflake } from "@fosscord/util";
-import { LessThan, IsNull } from "typeorm";
-import { route } from "@fosscord/api";
-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();
-	date.setDate(date.getDate() - days);
-	//Snowflake should have `generateFromTime` method? Or similar?
-	var minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
-
-	/**
-	idea: ability to customise the cutoff variable
-	possible candidates: public read receipt, last presence, last VC leave
-	**/
-	var members = await Member.find({
-		where: [
-			{
-				guild_id,
-				last_message_id: LessThan(minId.toString())
-			},
-			{
-				last_message_id: IsNull()
-			}
-		],
-		relations: ["roles"]
-	});
-	console.log(members);
-	if (!members.length) return [];
-
-	//I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
-	if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
-
-	const me = await Member.findOneOrFail({ 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 } });
-
-	members = members.filter(
-		(member) =>
-			member.id !== guild.owner_id && //can't kick owner
-			member.roles?.some(
-				(role) =>
-					role.position < myHighestRole || //roles higher than me can't be kicked
-					me.id === guild.owner_id //owner can kick anyone
-			)
-	);
-
-	return members;
-};
-
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const days = parseInt(req.query.days as string);
-
-	var 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[]);
-
-	res.send({ pruned: members.length });
-});
-
-export interface PruneSchema {
-	/**
-	 * @min 0
-	 */
-	days: number;
-}
-
-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;
-	if (typeof roles === "string") roles = [roles];
-
-	const { guild_id } = req.params;
-	const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
-
-	await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
-
-	res.send({ purged: members.length });
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts
deleted file mode 100644
index 75d24fd1..00000000
--- a/api/src/routes/guilds/#guild_id/regions.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Config, Guild, Member } from "@fosscord/util";
-import { Request, Response, Router } from "express";
-import { getVoiceRegions, route } from "@fosscord/api";
-import { getIpAdress } from "@fosscord/api";
-
-const router = Router();
-
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const guild = await Guild.findOneOrFail({ id: guild_id });
-	//TODO we should use an enum for guild's features and not hardcoded strings
-	return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS")));
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts
deleted file mode 100644
index 2ad01682..00000000
--- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Router, Request, Response } from "express";
-import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, handleFile } from "@fosscord/util";
-import { route } from "@fosscord/api";
-import { HTTPError } from "lambert-server";
-import { RoleModifySchema } from "../";
-
-const router = Router();
-
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id, role_id } = req.params;
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-	const role = await Role.findOneOrFail({ guild_id, id: role_id });
-	return res.json(role);
-});
-
-router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
-	const { guild_id, role_id } = req.params;
-	if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
-
-	await Promise.all([
-		Role.delete({
-			id: role_id,
-			guild_id: guild_id
-		}),
-		emitEvent({
-			event: "GUILD_ROLE_DELETE",
-			guild_id,
-			data: {
-				guild_id,
-				role_id
-			}
-		} as GuildRoleDeleteEvent)
-	]);
-
-	res.sendStatus(204);
-});
-
-// TODO: check role hierarchy
-
-router.patch("/", 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);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/roles/index.ts b/api/src/routes/guilds/#guild_id/roles/index.ts
deleted file mode 100644
index 53465105..00000000
--- a/api/src/routes/guilds/#guild_id/roles/index.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { Request, Response, Router } from "express";
-import {
-	Role,
-	getPermission,
-	Member,
-	GuildRoleCreateEvent,
-	GuildRoleUpdateEvent,
-	GuildRoleDeleteEvent,
-	emitEvent,
-	Config,
-	DiscordApiErrors,
-	handleFile
-} from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
-
-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 });
-
-	return res.json(roles);
-});
-
-router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
-	const guild_id = req.params.guild_id;
-	const body = req.body as RoleModifySchema;
-
-	const role_count = await Role.count({ guild_id });
-	const { maxRoles } = Config.get().limits.guild;
-
-	if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
-
-	const role = new Role({
-		// values before ...body are default and can be overriden
-		position: 0,
-		hoist: false,
-		color: 0,
-		mentionable: false,
-		...body,
-		guild_id: guild_id,
-		managed: false,
-		permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")),
-		tags: undefined,
-		icon: null,
-		unicode_emoji: null
-	});
-
-	await Promise.all([
-		role.save(),
-		emitEvent({
-			event: "GUILD_ROLE_CREATE",
-			guild_id,
-			data: {
-				guild_id,
-				role: role
-			}
-		} as GuildRoleCreateEvent)
-	]);
-
-	res.json(role);
-});
-
-router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const body = req.body as RolePositionUpdateSchema;
-
-	const perms = await getPermission(req.user_id, guild_id);
-	perms.hasThrow("MANAGE_ROLES");
-
-	await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
-
-	const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) });
-
-	await Promise.all(
-		roles.map((x) =>
-			emitEvent({
-				event: "GUILD_ROLE_UPDATE",
-				guild_id,
-				data: {
-					guild_id,
-					role: x
-				}
-			} as GuildRoleUpdateEvent)
-		)
-	);
-
-	res.json(roles);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/stickers.ts b/api/src/routes/guilds/#guild_id/stickers.ts
deleted file mode 100644
index 4ea1dce1..00000000
--- a/api/src/routes/guilds/#guild_id/stickers.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import {
-	emitEvent,
-	GuildStickersUpdateEvent,
-	handleFile,
-	Member,
-	Snowflake,
-	Sticker,
-	StickerFormatType,
-	StickerType,
-	uploadFile
-} from "@fosscord/util";
-import { Router, Request, Response } from "express";
-import { route } from "@fosscord/api";
-import multer from "multer";
-import { HTTPError } from "lambert-server";
-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 }));
-});
-
-const bodyParser = multer({
-	limits: {
-		fileSize: 1024 * 1024 * 100,
-		fields: 10,
-		files: 1
-	},
-	storage: multer.memoryStorage()
-}).single("file");
-
-router.post(
-	"/",
-	bodyParser,
-	route({ permission: "MANAGE_EMOJIS_AND_STICKERS", body: "ModifyGuildStickerSchema" }),
-	async (req: Request, res: Response) => {
-		if (!req.file) throw new HTTPError("missing file");
-
-		const { guild_id } = req.params;
-		const body = req.body as ModifyGuildStickerSchema;
-		const id = Snowflake.generate();
-
-		const [sticker] = await Promise.all([
-			new Sticker({
-				...body,
-				guild_id,
-				id,
-				type: StickerType.GUILD,
-				format_type: getStickerFormat(req.file.mimetype),
-				available: true
-			}).save(),
-			uploadFile(`/stickers/${id}`, req.file)
-		]);
-
-		await sendStickerUpdateEvent(guild_id);
-
-		res.json(sticker);
-	}
-);
-
-export function getStickerFormat(mime_type: string) {
-	switch (mime_type) {
-		case "image/apng":
-			return StickerFormatType.APNG;
-		case "application/json":
-			return StickerFormatType.LOTTIE;
-		case "image/png":
-			return StickerFormatType.PNG;
-		case "image/gif":
-			return StickerFormatType.GIF;
-		default:
-			throw new HTTPError("invalid sticker format: must be png, apng or lottie");
-	}
-}
-
-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 }));
-});
-
-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" }),
-	async (req: Request, res: Response) => {
-		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();
-		await sendStickerUpdateEvent(guild_id);
-
-		return res.json(sticker);
-	}
-);
-
-async function sendStickerUpdateEvent(guild_id: string) {
-	return emitEvent({
-		event: "GUILD_STICKERS_UPDATE",
-		guild_id: guild_id,
-		data: {
-			guild_id: guild_id,
-			stickers: await Sticker.find({ guild_id: guild_id })
-		}
-	} as GuildStickersUpdateEvent);
-}
-
-router.delete("/:sticker_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
-	const { guild_id, sticker_id } = req.params;
-
-	await Sticker.delete({ guild_id, id: sticker_id });
-	await sendStickerUpdateEvent(guild_id);
-
-	return res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts
deleted file mode 100644
index 5179e761..00000000
--- a/api/src/routes/guilds/#guild_id/templates.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Guild, Template } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
-import { generateCode } from "@fosscord/api";
-
-const router: Router = Router();
-
-const TemplateGuildProjection: (keyof Guild)[] = [
-	"name",
-	"description",
-	"region",
-	"verification_level",
-	"default_message_notifications",
-	"explicit_content_filter",
-	"preferred_locale",
-	"afk_timeout",
-	"roles",
-	// "channels",
-	"afk_channel_id",
-	"system_channel_id",
-	"system_channel_flags",
-	"icon"
-];
-
-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 });
-
-	return res.json(templates);
-});
-
-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) => {});
-	if (exists) throw new HTTPError("Template already exists", 400);
-
-	const template = await new Template({
-		...req.body,
-		code: generateCode(),
-		creator_id: req.user_id,
-		created_at: new Date(),
-		updated_at: new Date(),
-		source_guild_id: guild_id,
-		serialized_source_guild: guild
-	}).save();
-
-	res.json(template);
-});
-
-router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
-	const { code, guild_id } = req.params;
-
-	const template = await Template.delete({
-		code,
-		source_guild_id: guild_id
-	});
-
-	res.json(template);
-});
-
-router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
-	const { code, guild_id } = req.params;
-	const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
-
-	const template = await new Template({ code, serialized_source_guild: guild }).save();
-
-	res.json(template);
-});
-
-router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
-	const { code, guild_id } = req.params;
-	const { name, description } = req.body;
-
-	const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save();
-
-	res.json(template);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts
deleted file mode 100644
index 29cd25e2..00000000
--- a/api/src/routes/guilds/#guild_id/vanity-url.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util";
-import { Router, Request, Response } from "express";
-import { route } from "@fosscord/api";
-import { HTTPError } from "lambert-server";
-
-const router = Router();
-
-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 });
-
-	if (!guild.features.includes("ALIASABLE_NAMES")) {
-		const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } });
-		if (!invite) return res.json({ code: null });
-
-		return res.json({ code: invite.code, uses: invite.uses });
-	} else {
-		const invite = await Invite.find({ where: { guild_id: guild_id, vanity_url: true } });
-		if (!invite || invite.length == 0) return res.json({ code: null });
-
-		return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
-	}
-});
-
-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 });
-	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 });
-	if (invite) throw new HTTPError("Invite already exists");
-
-	const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT });
-
-	await new Invite({
-		vanity_url: true,
-		code: code,
-		temporary: false,
-		uses: 0,
-		max_uses: 0,
-		max_age: 0,
-		created_at: new Date(),
-		expires_at: new Date(),
-		guild_id: guild_id,
-		channel_id: id
-	}).save();
-
-	return res.json({ code: code });
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts
deleted file mode 100644
index f9fbea54..00000000
--- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
-import { route } from "@fosscord/api";
-import { Request, Response, Router } from "express";
-
-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;
-	if (user_id === "@me") user_id = req.user_id;
-
-	const perms = await getPermission(req.user_id, guild_id, body.channel_id);
-
-	/*
-	From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
-	You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself.
-	You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak.
-	 */
-	if (body.suppress && user_id !== req.user_id) {
-		perms.hasThrow("MUTE_MEMBERS");
-	}
-	if (!body.suppress) body.request_to_speak_timestamp = new Date();
-	if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
-
-	const voice_state = await VoiceState.findOne({
-		guild_id,
-		channel_id: body.channel_id,
-		user_id
-	});
-	if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
-
-	voice_state.assign(body);
-	const channel = await Channel.findOneOrFail({ guild_id, id: body.channel_id });
-	if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
-		throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
-	}
-
-	await Promise.all([
-		voice_state.save(),
-		emitEvent({
-			event: "VOICE_STATE_UPDATE",
-			data: voice_state,
-			guild_id
-		} as VoiceStateUpdateEvent)
-	]);
-	return res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/webhooks.ts b/api/src/routes/guilds/#guild_id/webhooks.ts
deleted file mode 100644
index 8b2febea..00000000
--- a/api/src/routes/guilds/#guild_id/webhooks.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-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 webhooks
-router.get("/", route({}), async (req: Request, res: Response) => {
-	res.json([]);
-});
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts
deleted file mode 100644
index 7141f17e..00000000
--- a/api/src/routes/guilds/#guild_id/welcome_screen.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Guild, getPermission, Snowflake, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-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 });
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-
-	res.json(guild.welcome_screen);
-});
-
-router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
-	const guild_id = req.params.guild_id;
-	const body = req.body as GuildUpdateWelcomeScreenSchema;
-
-	const guild = await Guild.findOneOrFail({ id: guild_id });
-
-	if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400);
-	if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
-	if (body.description) guild.welcome_screen.description = body.description;
-	if (body.enabled != null) guild.welcome_screen.enabled = body.enabled;
-
-	res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts
deleted file mode 100644
index c31519fa..00000000
--- a/api/src/routes/guilds/#guild_id/widget.json.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-import { random, route } from "@fosscord/api";
-
-const router: Router = Router();
-
-// Undocumented API notes:
-// An invite is created for the widget_channel_id on request (only if an existing one created by the widget doesn't already exist)
-// This invite created doesn't include an inviter object like user created ones and has a default expiry of 24 hours
-// Missing user object information is intentional (https://github.com/discord/discord-api-docs/issues/1287)
-// channels returns voice channel objects where @everyone has the CONNECT permission
-// members (max 100 returned) is a sample of all members, and bots par invisible status, there exists some alphabetical distribution pattern between the members returned
-
-// https://discord.com/developers/docs/resources/guild#get-guild-widget
-// TODO: Cache the response for a guild for 5 minutes regardless of response
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	const guild = await Guild.findOneOrFail({ 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 });
-
-	if (guild.widget_channel_id && !invite) {
-		// Create invite for channel if none exists
-		// TODO: Refactor invite create code to a shared function
-		const max_age = 86400; // 24 hours
-		const expires_at = new Date(max_age * 1000 + Date.now());
-		const body = {
-			code: random(),
-			temporary: false,
-			uses: 0,
-			max_uses: 0,
-			max_age: max_age,
-			expires_at,
-			created_at: new Date(),
-			guild_id,
-			channel_id: guild.widget_channel_id,
-			inviter_id: null
-		};
-
-		invite = await new Invite(body).save();
-	}
-
-	// Fetch voice channels, and the @everyone permissions object
-	const channels = [] as any[];
-
-	(await Channel.find({ where: { guild_id: guild_id, type: 2 }, order: { position: "ASC" } })).filter((doc) => {
-		// Only return channels where @everyone has the CONNECT permission
-		if (
-			doc.permission_overwrites === undefined ||
-			Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT
-		) {
-			channels.push({
-				id: doc.id,
-				name: doc.name,
-				position: doc.position
-			});
-		}
-	});
-
-	// 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 });
-
-	// Construct object to respond with
-	const data = {
-		id: guild_id,
-		name: guild.name,
-		instant_invite: invite?.code,
-		channels: channels,
-		members: members,
-		presence_count: guild.presence_count
-	};
-
-	res.set("Cache-Control", "public, max-age=300");
-	return res.json(data);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/api/src/routes/guilds/#guild_id/widget.png.ts
deleted file mode 100644
index 4c82b740..00000000
--- a/api/src/routes/guilds/#guild_id/widget.png.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Guild } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
-import fs from "fs";
-import path from "path";
-
-const router: Router = Router();
-
-// TODO: use svg templates instead of node-canvas for improved performance and to change it easily
-
-// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
-// TODO: Cache the response
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	const guild = await Guild.findOneOrFail({ id: guild_id });
-	if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
-
-	// Fetch guild information
-	const icon = guild.icon;
-	const name = guild.name;
-	const presence = guild.presence_count + " ONLINE";
-
-	// Fetch parameter
-	const style = req.query.style?.toString() || "shield";
-	if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
-		throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
-	}
-
-	// Setup canvas
-	const { createCanvas } = require("canvas");
-	const { loadImage } = require("canvas");
-	const sizeOf = require("image-size");
-
-	// TODO: Widget style templates need Fosscord branding
-	const source = path.join(__dirname, "..", "..", "..", "..", "assets", "widget", `${style}.png`);
-	if (!fs.existsSync(source)) {
-		throw new HTTPError("Widget template does not exist.", 400);
-	}
-
-	// Create base template image for parameter
-	const { width, height } = await sizeOf(source);
-	const canvas = createCanvas(width, height);
-	const ctx = canvas.getContext("2d");
-	const template = await loadImage(source);
-	ctx.drawImage(template, 0, 0);
-
-	// Add the guild specific information to the template asset image
-	switch (style) {
-		case "shield":
-			ctx.textAlign = "center";
-			await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence);
-			break;
-		case "banner1":
-			if (icon) await drawIcon(ctx, 20, 27, 50, icon);
-			await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
-			await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence);
-			break;
-		case "banner2":
-			if (icon) await drawIcon(ctx, 13, 19, 36, icon);
-			await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
-			await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence);
-			break;
-		case "banner3":
-			if (icon) await drawIcon(ctx, 20, 20, 50, icon);
-			await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
-			await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence);
-			break;
-		case "banner4":
-			if (icon) await drawIcon(ctx, 21, 136, 50, icon);
-			await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
-			await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence);
-			break;
-		default:
-			throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
-	}
-
-	// Return final image
-	const buffer = canvas.toBuffer("image/png");
-	res.set("Content-Type", "image/png");
-	res.set("Cache-Control", "public, max-age=3600");
-	return res.send(buffer);
-});
-
-async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) {
-	// @ts-ignore
-	const img = new require("canvas").Image();
-	img.src = icon;
-
-	// Do some canvas clipping magic!
-	canvas.save();
-	canvas.beginPath();
-
-	const r = scale / 2; // use scale to determine radius
-	canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center
-
-	canvas.clip();
-	canvas.drawImage(img, x, y, scale, scale);
-
-	canvas.restore();
-}
-
-async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) {
-	canvas.fillStyle = color;
-	canvas.font = font;
-	if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
-	canvas.fillText(text, x, y);
-}
-
-export default router;
diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts
deleted file mode 100644
index 2640618d..00000000
--- a/api/src/routes/guilds/#guild_id/widget.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Guild } 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 });
-
-	return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null });
-});
-
-// https://discord.com/developers/docs/resources/guild#modify-guild-widget
-router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
-	const body = req.body as WidgetModifySchema;
-	const { guild_id } = req.params;
-
-	await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id });
-	// Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
-
-	return res.json(body);
-});
-
-export default router;
diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts
deleted file mode 100644
index 10721413..00000000
--- a/api/src/routes/guilds/index.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Router, Request, Response } from "express";
-import { Role, Guild, Snowflake, Config, getRights, Member, Channel, DiscordApiErrors, handleFile } 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 rights = await getRights(req.user_id);
-	if ((guild_count >= maxGuilds)&&!rights.has("MANAGE_GUILDS")) {
-		throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
-	}
-
-	const guild = await Guild.createGuild({ ...body, owner_id: req.user_id });
-
-	const { autoJoin } = Config.get().guild;
-	if (autoJoin.enabled && !autoJoin.guilds?.length) {
-		// @ts-ignore
-		await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
-	}
-
-	await Member.addToGuild(req.user_id, guild.id);
-
-	res.status(201).json({ id: guild.id });
-});
-
-export default router;
diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts
deleted file mode 100644
index 3d922e85..00000000
--- a/api/src/routes/guilds/templates/index.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Request, Response, Router } from "express";
-import { Template, Guild, Role, Snowflake, Config, User, Member } 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);
-
-	const { code } = req.params;
-	
-	if (code.startsWith("discord:")) {
-		if (!allowDiscordTemplates)	return res.json({ code: 403, message: "Discord templates cannot be used on this instance." }).sendStatus(403);
-		const discordTemplateID = code.split("discord:", 2)[1];
-
-		const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, {
-			method: "get",
-			headers: { "Content-Type": "application/json" }
-		});
-		return res.json(await discordTemplateData.json());
-	}
-
-	if (code.startsWith("external:")) {
-		if (!allowRaws)	return res.json({ code: 403, message: "Importing raws is disabled on this instance." }).sendStatus(403);
-
-		return res.json(code.split("external:", 2)[1]);
-	}
-
-	const template = await Template.findOneOrFail({ code: code });
-	res.json(template);
-});
-
-router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
-	const { enabled, allowTemplateCreation, allowDiscordTemplates, allowRaws } = Config.get().templates;
-	if (!enabled) return res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403);
-	if (!allowTemplateCreation) return res.json({ code: 403, message: "Template creation is disabled on this instance." }).sendStatus(403);
-
-	const { code } = req.params;
-	const body = req.body as GuildTemplateCreateSchema;
-
-	const { maxGuilds } = Config.get().limits.user;
-
-	const guild_count = await Member.count({ id: req.user_id });
-	if (guild_count >= maxGuilds) {
-		throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
-	}
-
-	const template = await Template.findOneOrFail({ code: code });
-
-	const guild_id = Snowflake.generate();
-
-	const [guild, role] = await Promise.all([
-		new Guild({
-			...body,
-			...template.serialized_source_guild,
-			id: guild_id,
-			owner_id: req.user_id
-		}).save(),
-		new Role({
-			id: guild_id,
-			guild_id: guild_id,
-			color: 0,
-			hoist: false,
-			managed: true,
-			mentionable: true,
-			name: "@everyone",
-			permissions: BigInt("2251804225"),
-			position: 0,
-			tags: null
-		}).save()
-	]);
-
-	await Member.addToGuild(req.user_id, guild_id);
-
-	res.status(201).json({ id: guild.id });
-});
-
-export default router;