summary refs log tree commit diff
path: root/api/src/routes/channels/#channel_id
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-12 23:32:55 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-12 23:32:55 +0200
commitf93bd1fd67f38cd9dc9250f328bb1b8e1214e37c (patch)
tree9e8b85cbad3dbf65e4e4a1162f68163c72b3aea2 /api/src/routes/channels/#channel_id
parentMerge pull request #353 from AlTech98/dummy-routes (diff)
parent:sparkles: #307 done (diff)
downloadserver-f93bd1fd67f38cd9dc9250f328bb1b8e1214e37c.tar.xz
Merge branch 'typescript-interface-body-parser+autogenerate-unit-tests+documentation'
Diffstat (limited to 'api/src/routes/channels/#channel_id')
-rw-r--r--api/src/routes/channels/#channel_id/index.ts49
-rw-r--r--api/src/routes/channels/#channel_id/invites.ts42
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/ack.ts10
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/index.ts18
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts33
-rw-r--r--api/src/routes/channels/#channel_id/messages/bulk-delete.ts9
-rw-r--r--api/src/routes/channels/#channel_id/messages/index.ts105
-rw-r--r--api/src/routes/channels/#channel_id/permissions.ts82
-rw-r--r--api/src/routes/channels/#channel_id/pins.ts33
-rw-r--r--api/src/routes/channels/#channel_id/typing.ts6
-rw-r--r--api/src/routes/channels/#channel_id/webhooks.ts16
11 files changed, 220 insertions, 183 deletions
diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts
index 4aa5a5b9..02ac9884 100644
--- a/api/src/routes/channels/#channel_id/index.ts
+++ b/api/src/routes/channels/#channel_id/index.ts
@@ -1,48 +1,59 @@
-import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission } from "@fosscord/util";
+import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util";
 import { Router, Response, Request } from "express";
-import { HTTPError } from "lambert-server";
-import { ChannelModifySchema } from "../../../schema/Channel";
-import { check } from "../../../util/instanceOf";
+import { route } from "@fosscord/api";
 const router: Router = Router();
 // TODO: delete channel
 // TODO: Get channel
 
-router.get("/", async (req: Request, res: Response) => {
+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 permission = await getPermission(req.user_id, channel.guild_id, channel_id);
-	permission.hasThrow("VIEW_CHANNEL");
-
 	return res.send(channel);
 });
 
-router.delete("/", async (req: Request, res: Response) => {
+router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
 	const { channel_id } = req.params;
 
 	const channel = await Channel.findOneOrFail({ id: channel_id });
 
-	const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
-	permission.hasThrow("MANAGE_CHANNELS");
-
 	// TODO: Dm channel "close" not delete
 	const data = channel;
 
-	await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent);
-
-	await Channel.delete({ id: channel_id });
+	await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]);
 
 	res.send(data);
 });
 
-router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response) => {
+export interface ChannelModifySchema {
+	/**
+	 * @maxLength 100
+	 */
+	name: string;
+	type: ChannelType;
+	topic?: string;
+	bitrate?: number;
+	user_limit?: number;
+	rate_limit_per_user?: number;
+	position?: number;
+	permission_overwrites?: {
+		id: string;
+		type: ChannelPermissionOverwriteType;
+		allow: bigint;
+		deny: bigint;
+	}[];
+	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;
 	const { channel_id } = req.params;
 
-	const permission = await getPermission(req.user_id, undefined, channel_id);
-	permission.hasThrow("MANAGE_CHANNELS");
-
 	const channel = await Channel.findOneOrFail({ id: channel_id });
 	channel.assign(payload);
 
diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts
index fe22d3bc..39263185 100644
--- a/api/src/routes/channels/#channel_id/invites.ts
+++ b/api/src/routes/channels/#channel_id/invites.ts
@@ -1,14 +1,25 @@
 import { Router, Request, Response } from "express";
 import { HTTPError } from "lambert-server";
-import { check } from "../../../util/instanceOf";
-import { random } from "../../../util/RandomInviteID";
-import { InviteCreateSchema } from "../../../schema/Invite";
+import { route } from "@fosscord/api";
+import { random } from "@fosscord/api";
 import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util";
 import { isTextChannel } from "./messages";
 
 const router: Router = Router();
 
-router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) => {
+export interface InviteCreateSchema {
+	target_user_id?: string;
+	target_type?: string;
+	validate?: string; //? wtf 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" }), async (req: Request, res: Response) => {
 	const { user_id } = req;
 	const { channel_id } = req.params;
 	const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] });
@@ -19,23 +30,6 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response)
 	}
 	const { guild_id } = channel;
 
-	const permission = await getPermission(user_id, guild_id, undefined, {
-		guild_select: [
-			"banner",
-			"description",
-			"features",
-			"icon",
-			"id",
-			"name",
-			"nsfw",
-			"nsfw_level",
-			"splash",
-			"vanity_url_code",
-			"verification_level"
-		] as (keyof Guild)[]
-	});
-	permission.hasThrow("CREATE_INSTANT_INVITE");
-
 	const expires_at = new Date(req.body.max_age * 1000 + Date.now());
 
 	const invite = await new Invite({
@@ -52,14 +46,14 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response)
 	}).save();
 	const data = invite.toJSON();
 	data.inviter = await User.getPublicUser(req.user_id);
-	data.guild = permission.cache.guild;
+	data.guild = await Guild.findOne({ id: guild_id });
 	data.channel = channel;
 
 	await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent);
 	res.status(201).send(data);
 });
 
-router.get("/", async (req: Request, res: Response) => {
+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 });
@@ -68,8 +62,6 @@ router.get("/", async (req: Request, res: Response) => {
 		throw new HTTPError("This channel doesn't exist", 404);
 	}
 	const { guild_id } = channel;
-	const permission = await getPermission(user_id, guild_id);
-	permission.hasThrow("MANAGE_CHANNELS");
 
 	const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation });
 
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
index 0fd5f2be..97d1d19e 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -1,14 +1,18 @@
 import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util";
 import { Request, Response, Router } from "express";
-
-import { check } from "../../../../../util/instanceOf";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
 // TODO: check if message exists
 // TODO: send read state event to all channel members
 
-router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req: Request, res: Response) => {
+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);
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
index 7a00de43..d0f780db 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -1,12 +1,13 @@
 import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util";
 import { Router, Response, Request } from "express";
-import { MessageCreateSchema } from "../../../../../schema/Message";
-import { check } from "../../../../../util/instanceOf";
-import { handleMessage, postHandleMessage } from "../../../../../util/Message";
+import { route } from "@fosscord/api";
+import { handleMessage, postHandleMessage } from "@fosscord/api";
+import { MessageCreateSchema } from "../index";
 
 const router = Router();
+// TODO: message content/embed string length limit
 
-router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response) => {
+router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
 	const { message_id, channel_id } = req.params;
 	var body = req.body as MessageCreateSchema;
 
@@ -47,14 +48,17 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response
 
 // TODO: delete attachments in message
 
-router.delete("/", async (req: Request, res: Response) => {
+// permission check only if deletes messagr from other user
+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 permission = await getPermission(req.user_id, channel.guild_id, channel_id);
-	if (message.author_id !== req.user_id) permission.hasThrow("MANAGE_MESSAGES");
+	if (message.author_id !== req.user_id) {
+		const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
+		permission.hasThrow("MANAGE_MESSAGES");
+	}
 
 	await Message.delete({ id: message_id });
 
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
index f60484b5..f2b83d40 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -13,6 +13,7 @@ import {
 	PublicUserProjection,
 	User
 } from "@fosscord/util";
+import { route } from "@fosscord/api";
 import { Router, Response, Request } from "express";
 import { HTTPError } from "lambert-server";
 import { In } from "typeorm";
@@ -35,14 +36,11 @@ function getEmoji(emoji: string): PartialEmoji {
 	};
 }
 
-router.delete("/", async (req: Request, res: Response) => {
+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 permissions = await getPermission(req.user_id, undefined, channel_id);
-	permissions.hasThrow("MANAGE_MESSAGES");
-
 	await Message.update({ id: message_id, channel_id }, { reactions: [] });
 
 	await emitEvent({
@@ -58,13 +56,10 @@ router.delete("/", async (req: Request, res: Response) => {
 	res.sendStatus(204);
 });
 
-router.delete("/:emoji", async (req: Request, res: Response) => {
+router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => {
 	const { message_id, channel_id } = req.params;
 	const emoji = getEmoji(req.params.emoji);
 
-	const permissions = await getPermission(req.user_id, undefined, channel_id);
-	permissions.hasThrow("MANAGE_MESSAGES");
-
 	const message = await Message.findOneOrFail({ id: message_id, channel_id });
 
 	const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
@@ -88,7 +83,7 @@ router.delete("/:emoji", async (req: Request, res: Response) => {
 	res.sendStatus(204);
 });
 
-router.get("/:emoji", async (req: Request, res: Response) => {
+router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
 	const { message_id, channel_id } = req.params;
 	const emoji = getEmoji(req.params.emoji);
 
@@ -96,9 +91,6 @@ router.get("/:emoji", async (req: Request, res: Response) => {
 	const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
 	if (!reaction) throw new HTTPError("Reaction not found", 404);
 
-	const permissions = await getPermission(req.user_id, undefined, channel_id);
-	permissions.hasThrow("VIEW_CHANNEL");
-
 	const users = await User.find({
 		where: {
 			id: In(reaction.user_ids)
@@ -109,7 +101,7 @@ router.get("/:emoji", async (req: Request, res: Response) => {
 	res.json(users);
 });
 
-router.put("/:emoji/:user_id", async (req: Request, res: Response) => {
+router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY" }), async (req: Request, res: Response) => {
 	const { message_id, channel_id, user_id } = req.params;
 	if (user_id !== "@me") throw new HTTPError("Invalid user");
 	const emoji = getEmoji(req.params.emoji);
@@ -118,13 +110,11 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => {
 	const message = await Message.findOneOrFail({ id: message_id, channel_id });
 	const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
 
-	const permissions = await getPermission(req.user_id, undefined, channel_id);
-	permissions.hasThrow("READ_MESSAGE_HISTORY");
-	if (!already_added) permissions.hasThrow("ADD_REACTIONS");
+	if (!already_added) req.permission!.hasThrow("ADD_REACTIONS");
 
 	if (emoji.id) {
 		const external_emoji = await Emoji.findOneOrFail({ id: emoji.id });
-		if (!already_added) permissions.hasThrow("USE_EXTERNAL_EMOJIS");
+		if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS");
 		emoji.animated = external_emoji.animated;
 		emoji.name = external_emoji.name;
 	}
@@ -154,7 +144,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => {
 	res.sendStatus(204);
 });
 
-router.delete("/:emoji/:user_id", async (req: Request, res: Response) => {
+router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => {
 	var { message_id, channel_id, user_id } = req.params;
 
 	const emoji = getEmoji(req.params.emoji);
@@ -162,10 +152,11 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => {
 	const channel = await Channel.findOneOrFail({ id: channel_id });
 	const message = await Message.findOneOrFail({ id: message_id, channel_id });
 
-	const permissions = await getPermission(req.user_id, undefined, channel_id);
-
 	if (user_id === "@me") user_id = req.user_id;
-	else permissions.hasThrow("MANAGE_MESSAGES");
+	else {
+		const permissions = await getPermission(req.user_id, undefined, channel_id);
+		permissions.hasThrow("MANAGE_MESSAGES");
+	}
 
 	const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
 	if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts
index 5c486676..a0fe7cc0 100644
--- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -1,18 +1,21 @@
 import { Router, Response, Request } from "express";
 import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
-
-import { check } from "../../../../util/instanceOf";
+import { route } from "@fosscord/api";
 import { In } from "typeorm";
 
 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?
 // https://discord.com/developers/docs/resources/channel#bulk-delete-messages
-router.post("/", check({ messages: [String] }), async (req: Request, res: Response) => {
+router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => {
 	const { channel_id } = req.params;
 	const channel = await Channel.findOneOrFail({ id: channel_id });
 	if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts
index ad590d05..11334367 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -1,12 +1,10 @@
 import { Router, Response, Request } from "express";
-import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util";
+import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
-import { MessageCreateSchema } from "../../../../schema/Message";
-import { check, instanceOf, Length } from "../../../../util/instanceOf";
+import { instanceOf, Length, route } from "@fosscord/api";
 import multer from "multer";
-import { Query } from "mongoose";
-import { sendMessage } from "../../../../util/Message";
-import { uploadFile } from "../../../../util/cdn";
+import { sendMessage } from "@fosscord/api";
+import { uploadFile } from "@fosscord/api";
 import { FindManyOptions, LessThan, MoreThan } from "typeorm";
 
 const router: Router = Router();
@@ -31,6 +29,30 @@ 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;
+}
+
 // https://discord.com/developers/docs/resources/channel#create-message
 // get messages
 router.get("/", async (req: Request, res: Response) => {
@@ -109,39 +131,44 @@ const messageUpload = multer({
 // TODO: check allowed_mentions
 
 // Send message
-router.post("/", messageUpload.single("file"), async (req: Request, res: Response) => {
-	const { channel_id } = req.params;
-	var body = req.body as MessageCreateSchema;
-	const attachments: Attachment[] = [];
-
-	if (req.file) {
-		try {
-			const file = await uploadFile(`/attachments/${channel_id}`, req.file);
-			attachments.push({ ...file, proxy_url: file.url });
-		} catch (error) {
-			return res.status(400).json(error);
+router.post(
+	"/",
+	messageUpload.single("file"),
+	async (req, res, next) => {
+		if (req.body.payload_json) {
+			req.body = JSON.parse(req.body.payload_json);
 		}
-	}
 
-	if (body.payload_json) {
-		body = JSON.parse(body.payload_json);
-	}
+		next();
+	},
+	route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }),
+	async (req: Request, res: Response) => {
+		const { channel_id } = req.params;
+		var body = req.body as MessageCreateSchema;
+		const attachments: Attachment[] = [];
+
+		if (req.file) {
+			try {
+				const file = 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 errors = instanceOf(MessageCreateSchema, body, { req });
-	if (errors !== true) throw errors;
-
-	const embeds = [];
-	if (body.embed) embeds.push(body.embed);
-	const data = await sendMessage({
-		...body,
-		type: 0,
-		pinned: false,
-		author_id: req.user_id,
-		embeds,
-		channel_id,
-		attachments,
-		edited_timestamp: undefined
-	});
-
-	return res.json(data);
-});
+		const embeds = [];
+		if (body.embed) embeds.push(body.embed);
+		const data = await sendMessage({
+			...body,
+			type: 0,
+			pinned: false,
+			author_id: req.user_id,
+			embeds,
+			channel_id,
+			attachments,
+			edited_timestamp: undefined
+		});
+
+		return res.json(data);
+	}
+);
diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts
index 9c49542b..827e46f2 100644
--- a/api/src/routes/channels/#channel_id/permissions.ts
+++ b/api/src/routes/channels/#channel_id/permissions.ts
@@ -2,61 +2,61 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get
 import { Router, Response, Request } from "express";
 import { HTTPError } from "lambert-server";
 
-import { check } from "../../../util/instanceOf";
+import { check, 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)
 
-router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, id: String }), async (req: Request, res: Response) => {
-	const { channel_id, overwrite_id } = req.params;
-	const body = req.body as { allow: bigint; deny: bigint; type: number; id: string };
+export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite {}
 
-	var channel = await Channel.findOneOrFail({ id: channel_id });
-	if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
+router.put(
+	"/:overwrite_id",
+	route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }),
+	async (req: Request, res: Response) => {
+		const { channel_id, overwrite_id } = req.params;
+		const body = req.body as { allow: bigint; deny: bigint; type: number; id: string };
 
-	const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
-	permissions.hasThrow("MANAGE_ROLES");
+		var channel = await Channel.findOneOrFail({ 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);
-	} else if (body.type === 1) {
-		if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404);
-	} else throw new HTTPError("type not supported", 501);
+		if (body.type === 0) {
+			if (!(await Role.count({ 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);
+		} else throw new HTTPError("type not supported", 501);
 
-	// @ts-ignore
-	var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id);
-	if (!overwrite) {
 		// @ts-ignore
-		overwrite = {
-			id: overwrite_id,
-			type: body.type,
-			allow: body.allow,
-			deny: body.deny
-		};
-		channel.permission_overwrites.push(overwrite);
+		var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id);
+		if (!overwrite) {
+			// @ts-ignore
+			overwrite = {
+				id: overwrite_id,
+				type: body.type,
+				allow: body.allow,
+				deny: body.deny
+			};
+			channel.permission_overwrites.push(overwrite);
+		}
+		overwrite.allow = body.allow;
+		overwrite.deny = body.deny;
+
+		await Promise.all([
+			channel.save(),
+			emitEvent({
+				event: "CHANNEL_UPDATE",
+				channel_id,
+				data: channel
+			} as ChannelUpdateEvent)
+		]);
+
+		return res.sendStatus(204);
 	}
-	overwrite.allow = body.allow;
-	overwrite.deny = body.deny;
-
-	// @ts-ignore
-	channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, channel, { new: true });
-
-	await emitEvent({
-		event: "CHANNEL_UPDATE",
-		channel_id,
-		data: channel
-	} as ChannelUpdateEvent);
-
-	return res.sendStatus(204);
-});
+);
 
 // TODO: check permission hierarchy
-router.delete("/:overwrite_id", async (req: Request, res: Response) => {
+router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
 	const { channel_id, overwrite_id } = req.params;
 
-	const permissions = await getPermission(req.user_id, undefined, channel_id);
-	permissions.hasThrow("MANAGE_ROLES");
-
 	const channel = await Channel.findOneOrFail({ id: channel_id });
 	if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
 
diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts
index 33309c86..e71e659f 100644
--- a/api/src/routes/channels/#channel_id/pins.ts
+++ b/api/src/routes/channels/#channel_id/pins.ts
@@ -1,19 +1,26 @@
-import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent } from "@fosscord/util";
+import {
+	Channel,
+	ChannelPinsUpdateEvent,
+	Config,
+	emitEvent,
+	getPermission,
+	Message,
+	MessageUpdateEvent,
+	DiscordApiErrors
+} from "@fosscord/util";
 import { Router, Request, Response } from "express";
 import { HTTPError } from "lambert-server";
-import { DiscordApiErrors } from "@fosscord/util";
+import { route } from "@fosscord/api";
 
 const router: Router = Router();
 
-router.put("/:message_id", async (req: Request, res: Response) => {
+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 permission = await getPermission(req.user_id, message.guild_id, channel_id);
-	permission.hasThrow("VIEW_CHANNEL");
 
 	// * in dm channels anyone can pin messages -> only check for guilds
-	if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES");
+	if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
 
 	const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true });
 	const { maxPins } = Config.get().limits.channel;
@@ -26,7 +33,6 @@ router.put("/:message_id", async (req: Request, res: Response) => {
 			channel_id,
 			data: message
 		} as MessageUpdateEvent),
-
 		emitEvent({
 			event: "CHANNEL_PINS_UPDATE",
 			channel_id,
@@ -41,14 +47,11 @@ router.put("/:message_id", async (req: Request, res: Response) => {
 	res.sendStatus(204);
 });
 
-router.delete("/:message_id", async (req: Request, res: Response) => {
+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 permission = await getPermission(req.user_id, channel.guild_id, channel_id);
-	permission.hasThrow("VIEW_CHANNEL");
-	if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES");
+	if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
 
 	const message = await Message.findOneOrFail({ id: message_id });
 	message.pinned = false;
@@ -76,13 +79,9 @@ router.delete("/:message_id", async (req: Request, res: Response) => {
 	res.sendStatus(204);
 });
 
-router.get("/", async (req: Request, res: Response) => {
+router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => {
 	const { channel_id } = req.params;
 
-	const channel = await Channel.findOneOrFail({ id: channel_id });
-	const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
-	permission.hasThrow("VIEW_CHANNEL");
-
 	let pins = await Message.find({ channel_id: channel_id, pinned: true });
 
 	res.send(pins);
diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts
index f1fb3c86..ad973bca 100644
--- a/api/src/routes/channels/#channel_id/typing.ts
+++ b/api/src/routes/channels/#channel_id/typing.ts
@@ -1,11 +1,10 @@
 import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util";
+import { route } from "@fosscord/api";
 import { Router, Request, Response } from "express";
 
-import { HTTPError } from "lambert-server";
-
 const router: Router = Router();
 
-router.post("/", async (req: Request, res: Response) => {
+router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
 	const { channel_id } = req.params;
 	const user_id = req.user_id;
 	const timestamp = Date.now();
@@ -24,6 +23,7 @@ router.post("/", async (req: Request, res: Response) => {
 			guild_id: channel.guild_id
 		}
 	} as TypingStartEvent);
+
 	res.sendStatus(204);
 });
 
diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts
index e4125879..f84dfcc5 100644
--- a/api/src/routes/channels/#channel_id/webhooks.ts
+++ b/api/src/routes/channels/#channel_id/webhooks.ts
@@ -1,5 +1,5 @@
 import { Router, Response, Request } from "express";
-import { check, Length } from "../../../util/instanceOf";
+import { check, Length, route } from "@fosscord/api";
 import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { isTextChannel } from "./messages/index";
@@ -7,9 +7,16 @@ import { DiscordApiErrors } from "@fosscord/util";
 
 const router: Router = Router();
 // TODO: webhooks
+export interface WebhookCreateSchema {
+	/**
+	 * @maxLength 80
+	 */
+	name: string;
+	avatar: string;
+}
 
 // TODO: use Image Data Type for avatar instead of String
-router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req: Request, res: Response) => {
+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 });
 
@@ -20,12 +27,11 @@ router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), as
 	const { maxWebhooks } = Config.get().limits.channel;
 	if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
 
-	const permission = await getPermission(req.user_id, channel.guild_id);
-	permission.hasThrow("MANAGE_WEBHOOKS");
-
 	var { 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
 });
 
 export default router;