diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts
index 4aa5a5b9..61c851e8 100644
--- a/api/src/routes/channels/#channel_id/index.ts
+++ b/api/src/routes/channels/#channel_id/index.ts
@@ -1,47 +1,81 @@
-import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission } 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 {
+ Channel,
+ ChannelDeleteEvent,
+ ChannelPermissionOverwriteType,
+ ChannelType,
+ ChannelUpdateEvent,
+ emitEvent,
+ Recipient,
+ handleFile
+} from "@fosscord/util";
+import { Request, Response, Router } from "express";
+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;
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
- await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent);
+ if (channel.type === ChannelType.DM) {
+ const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } });
+ recipient.closed = true;
+ await Promise.all([
+ recipient.save(),
+ emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent)
+ ]);
+ } else if (channel.type === ChannelType.GROUP_DM) {
+ await Channel.removeRecipientFromChannel(channel, req.user_id);
+ } else {
+ await Promise.all([
+ Channel.delete({ id: channel_id }),
+ emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent)
+ ]);
+ }
- await Channel.delete({ id: channel_id });
-
- res.send(data);
+ res.send(channel);
});
-router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response) => {
+export interface ChannelModifySchema {
+ /**
+ * @maxLength 100
+ */
+ name?: string;
+ type?: ChannelType;
+ topic?: string;
+ icon?: string | null;
+ bitrate?: number;
+ user_limit?: number;
+ rate_limit_per_user?: number;
+ position?: number;
+ permission_overwrites?: {
+ id: string;
+ type: ChannelPermissionOverwriteType;
+ allow: 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");
+ if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
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..22420983 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; // ? what is this
+ max_age?: number;
+ max_uses?: number;
+ temporary?: boolean;
+ unique?: boolean;
+ target_user?: string;
+ target_user_type?: number;
+}
+
+router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE" }), 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..786e4581 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);
@@ -22,7 +26,7 @@ router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req
data: {
channel_id,
message_id,
- version: 496
+ version: 3763
}
} as MessageAckEvent);
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..7f7de264 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,16 +1,17 @@
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;
- const message = await Message.findOneOrFail({ id: message_id, channel_id });
+ const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] });
const permissions = await getPermission(req.user_id, undefined, channel_id);
@@ -45,16 +46,17 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response
return res.json(message);
});
-// 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..fab20977 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -1,12 +1,19 @@
import { Router, Response, Request } from "express";
-import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util";
+import {
+ Attachment,
+ Channel,
+ ChannelType,
+ DmChannelDTO,
+ Embed,
+ emitEvent,
+ getPermission,
+ Message,
+ MessageCreateEvent,
+ uploadFile
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
-import { MessageCreateSchema } from "../../../../schema/Message";
-import { check, instanceOf, Length } from "../../../../util/instanceOf";
+import { handleMessage, postHandleMessage, route } from "@fosscord/api";
import multer from "multer";
-import { Query } from "mongoose";
-import { sendMessage } from "../../../../util/Message";
-import { uploadFile } from "../../../../util/cdn";
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
const router: Router = Router();
@@ -31,6 +38,31 @@ export function isTextChannel(type: ChannelType): boolean {
}
}
+export interface MessageCreateSchema {
+ content?: string;
+ nonce?: string;
+ tts?: boolean;
+ flags?: string;
+ embeds?: Embed[];
+ embed?: Embed;
+ // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object)
+ allowed_mentions?: {
+ parse?: string[];
+ roles?: string[];
+ users?: string[];
+ replied_user?: boolean;
+ };
+ message_reference?: {
+ message_id: string;
+ channel_id: string;
+ guild_id?: string;
+ fail_if_not_exists?: boolean;
+ };
+ payload_json?: string;
+ file?: any;
+ attachments?: any[]; //TODO we should create an interface for attachments
+}
+
// https://discord.com/developers/docs/resources/channel#create-message
// get messages
router.get("/", async (req: Request, res: Response) => {
@@ -39,17 +71,12 @@ router.get("/", async (req: Request, res: Response) => {
if (!channel) throw new HTTPError("Channel not found", 404);
isTextChannel(channel.type);
+ const around = req.query.around ? `${req.query.around}` : undefined;
+ const before = req.query.before ? `${req.query.before}` : undefined;
+ const after = req.query.after ? `${req.query.after}` : undefined;
+ const limit = Number(req.query.limit) || 50;
+ if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100");
- try {
- instanceOf({ $around: String, $after: String, $before: String, $limit: new Length(Number, 1, 100) }, req.query, {
- path: "query",
- req
- });
- } catch (error) {
- return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error });
- }
- var { around, after, before, limit }: { around?: string; after?: string; before?: string; limit?: number } = req.query;
- if (!limit) limit = 50;
var halfLimit = Math.floor(limit / 2);
const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
@@ -109,39 +136,77 @@ 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[] = [];
+router.post(
+ "/",
+ messageUpload.single("file"),
+ async (req, res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- 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);
+ 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 channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
- if (body.payload_json) {
- body = JSON.parse(body.payload_json);
- }
+ const embeds = [];
+ if (body.embed) embeds.push(body.embed);
+ let message = await handleMessage({
+ ...body,
+ type: 0,
+ pinned: false,
+ author_id: req.user_id,
+ embeds,
+ channel_id,
+ attachments,
+ edited_timestamp: undefined,
+ timestamp: new Date()
+ });
- const errors = instanceOf(MessageCreateSchema, body, { req });
- if (errors !== true) throw errors;
+ message = await message.save();
- 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
- });
+ await channel.assign({ last_message_id: message.id }).save();
- return res.json(data);
-});
+ if (channel.isDm()) {
+ const channel_dto = await DmChannelDTO.from(channel);
+
+ for (let recipient of channel.recipients!) {
+ if (recipient.closed) {
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto.excludedRecipients([recipient.user_id]),
+ user_id: recipient.user_id
+ });
+ }
+ }
+
+ //Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
+ await Promise.all(
+ channel
+ .recipients!.filter((r) => r.closed)
+ .map(async (r) => {
+ r.closed = false;
+ return await r.save();
+ })
+ );
+ }
+
+ await emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent);
+ postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
+
+ return res.json(message);
+ }
+);
diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts
index 9c49542b..bc7ad5b8 100644
--- a/api/src/routes/channels/#channel_id/permissions.ts
+++ b/api/src/routes/channels/#channel_id/permissions.ts
@@ -2,65 +2,65 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get
import { Router, Response, Request } from "express";
import { HTTPError } from "lambert-server";
-import { check } from "../../../util/instanceOf";
+import { route } from "@fosscord/api";
const router: Router = Router();
// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel)
-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);
- }
- overwrite.allow = body.allow;
- overwrite.deny = body.deny;
-
- // @ts-ignore
- channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, channel, { new: true });
+ 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 emitEvent({
- event: "CHANNEL_UPDATE",
- channel_id,
- data: channel
- } as ChannelUpdateEvent);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ channel_id,
+ data: channel
+ } as ChannelUpdateEvent)
+ ]);
- return res.sendStatus(204);
-});
+ 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);
- channel.permission_overwrites = channel.permission_overwrites.filter((x) => x.id === overwrite_id);
+ channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id);
await Promise.all([
channel.save(),
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/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts
index ea6bc563..83b62049 100644
--- a/api/src/routes/channels/#channel_id/recipients.ts
+++ b/api/src/routes/channels/#channel_id/recipients.ts
@@ -1,5 +1,58 @@
-import { Router, Response, Request } from "express";
+import { Request, Response, Router } from "express";
+import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";
+import { route } from "@fosscord/api"
+
const router: Router = Router();
-// TODO:
+
+router.put("/:user_id", route({}), async (req: Request, res: Response) => {
+ const { channel_id, user_id } = req.params;
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
+
+ if (channel.type !== ChannelType.GROUP_DM) {
+ const recipients = [
+ ...channel.recipients!.map(r => r.user_id),
+ user_id
+ ].unique()
+
+ const new_channel = await Channel.createDMChannel(recipients, req.user_id)
+ return res.status(201).json(new_channel);
+ } else {
+ if (channel.recipients!.map(r => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error?
+ }
+
+ channel.recipients!.push(new Recipient({ channel_id: channel_id, user_id: user_id }));
+ await channel.save()
+
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id
+ });
+
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_ADD", data: {
+ channel_id: channel_id,
+ user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
+ }, channel_id: channel_id
+ } as ChannelRecipientAddEvent);
+ return res.sendStatus(204);
+ }
+});
+
+router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
+ const { channel_id, user_id } = req.params;
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
+ if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id)))
+ throw DiscordApiErrors.MISSING_PERMISSIONS
+
+ if (!channel.recipients!.map(r => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error?
+ }
+
+ await Channel.removeRecipientFromChannel(channel, user_id)
+
+ return res.sendStatus(204);
+});
export default router;
diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts
index f1fb3c86..a9dcb315 100644
--- a/api/src/routes/channels/#channel_id/typing.ts
+++ b/api/src/routes/channels/#channel_id/typing.ts
@@ -1,29 +1,29 @@
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();
const channel = await Channel.findOneOrFail({ id: channel_id });
- const member = await Member.findOneOrFail({ id: user_id });
+ const member = await Member.findOneOrFail({ where: { id: user_id }, relations: ["roles"] });
await emitEvent({
event: "TYPING_START",
channel_id: channel_id,
data: {
// this is the paylod
- member: { ...member, roles: member.roles.map((x) => x.id) },
+ member: { ...member, roles: member.roles?.map((x) => x.id) },
channel_id,
timestamp,
user_id,
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..7b894455 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 { 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;
|