From 66df10d6b02cb1bed437665bc293dbcd5b9c73ff Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Sat, 14 Jan 2023 13:08:48 +0100 Subject: Move endpoints to respective versions, split out non implemented routes Signed-off-by: TheArcaneBrony --- src/api/routes/guilds/#guild_id/audit-logs.ts | 17 -- src/api/routes/guilds/#guild_id/bans.ts | 200 --------------------- src/api/routes/guilds/#guild_id/channels.ts | 86 --------- src/api/routes/guilds/#guild_id/delete.ts | 44 ----- .../guilds/#guild_id/discovery-requirements.ts | 39 ---- src/api/routes/guilds/#guild_id/emojis.ts | 148 --------------- src/api/routes/guilds/#guild_id/index.ts | 129 ------------- src/api/routes/guilds/#guild_id/integrations.ts | 9 - src/api/routes/guilds/#guild_id/invites.ts | 22 --- .../routes/guilds/#guild_id/member-verification.ts | 14 -- .../guilds/#guild_id/members/#member_id/index.ts | 130 -------------- .../guilds/#guild_id/members/#member_id/nick.ts | 26 --- .../members/#member_id/roles/#role_id/index.ts | 29 --- src/api/routes/guilds/#guild_id/members/index.ts | 32 ---- src/api/routes/guilds/#guild_id/messages/search.ts | 137 -------------- src/api/routes/guilds/#guild_id/premium.ts | 10 -- src/api/routes/guilds/#guild_id/profile/index.ts | 48 ----- src/api/routes/guilds/#guild_id/prune.ts | 106 ----------- src/api/routes/guilds/#guild_id/regions.ts | 20 --- .../guilds/#guild_id/roles/#role_id/index.ts | 92 ---------- src/api/routes/guilds/#guild_id/roles/index.ts | 123 ------------- src/api/routes/guilds/#guild_id/stickers.ts | 137 -------------- src/api/routes/guilds/#guild_id/templates.ts | 116 ------------ src/api/routes/guilds/#guild_id/vanity-url.ts | 82 --------- .../#guild_id/voice-states/#user_id/index.ts | 71 -------- src/api/routes/guilds/#guild_id/webhooks.ts | 9 - src/api/routes/guilds/#guild_id/welcome-screen.ts | 43 ----- src/api/routes/guilds/#guild_id/widget.json.ts | 97 ---------- src/api/routes/guilds/#guild_id/widget.png.ts | 179 ------------------ src/api/routes/guilds/#guild_id/widget.ts | 40 ----- src/api/routes/guilds/index.ts | 47 ----- src/api/routes/guilds/templates/index.ts | 132 -------------- 32 files changed, 2414 deletions(-) delete mode 100644 src/api/routes/guilds/#guild_id/audit-logs.ts delete mode 100644 src/api/routes/guilds/#guild_id/bans.ts delete mode 100644 src/api/routes/guilds/#guild_id/channels.ts delete mode 100644 src/api/routes/guilds/#guild_id/delete.ts delete mode 100644 src/api/routes/guilds/#guild_id/discovery-requirements.ts delete mode 100644 src/api/routes/guilds/#guild_id/emojis.ts delete mode 100644 src/api/routes/guilds/#guild_id/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/integrations.ts delete mode 100644 src/api/routes/guilds/#guild_id/invites.ts delete mode 100644 src/api/routes/guilds/#guild_id/member-verification.ts delete mode 100644 src/api/routes/guilds/#guild_id/members/#member_id/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/members/#member_id/nick.ts delete mode 100644 src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/members/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/messages/search.ts delete mode 100644 src/api/routes/guilds/#guild_id/premium.ts delete mode 100644 src/api/routes/guilds/#guild_id/profile/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/prune.ts delete mode 100644 src/api/routes/guilds/#guild_id/regions.ts delete mode 100644 src/api/routes/guilds/#guild_id/roles/#role_id/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/roles/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/stickers.ts delete mode 100644 src/api/routes/guilds/#guild_id/templates.ts delete mode 100644 src/api/routes/guilds/#guild_id/vanity-url.ts delete mode 100644 src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts delete mode 100644 src/api/routes/guilds/#guild_id/webhooks.ts delete mode 100644 src/api/routes/guilds/#guild_id/welcome-screen.ts delete mode 100644 src/api/routes/guilds/#guild_id/widget.json.ts delete mode 100644 src/api/routes/guilds/#guild_id/widget.png.ts delete mode 100644 src/api/routes/guilds/#guild_id/widget.ts delete mode 100644 src/api/routes/guilds/index.ts delete mode 100644 src/api/routes/guilds/templates/index.ts (limited to 'src/api/routes/guilds') diff --git a/src/api/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts deleted file mode 100644 index 76a11f6b..00000000 --- a/src/api/routes/guilds/#guild_id/audit-logs.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -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/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts deleted file mode 100644 index 930985d7..00000000 --- a/src/api/routes/guilds/#guild_id/bans.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { Request, Response, Router } from "express"; -import { - DiscordApiErrors, - emitEvent, - GuildBanAddEvent, - GuildBanRemoveEvent, - Ban, - User, - Member, - BanRegistrySchema, - BanModeratorSchema, -} from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { getIpAdress, route } from "@fosscord/api"; - -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({ where: { 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({ - where: { 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 = Ban.create({ - 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 = Ban.create({ - 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({ - where: { 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/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts deleted file mode 100644 index eae93607..00000000 --- a/src/api/routes/guilds/#guild_id/channels.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Router, Response, Request } from "express"; -import { - Channel, - ChannelUpdateEvent, - emitEvent, - ChannelModifySchema, - ChannelReorderSchema, -} from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; -const router = Router(); - -router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const channels = await Channel.find({ where: { 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); - }, -); - -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({ - where: { 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/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts deleted file mode 100644 index b951e4f4..00000000 --- a/src/api/routes/guilds/#guild_id/delete.ts +++ /dev/null @@ -1,44 +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/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts deleted file mode 100644 index 7e63c06b..00000000 --- a/src/api/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/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts deleted file mode 100644 index 6e8570eb..00000000 --- a/src/api/routes/guilds/#guild_id/emojis.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Router, Request, Response } from "express"; -import { - Config, - DiscordApiErrors, - emitEvent, - Emoji, - GuildEmojisUpdateEvent, - handleFile, - Member, - Snowflake, - User, - EmojiCreateSchema, - EmojiModifySchema, -} from "@fosscord/util"; -import { route } from "@fosscord/api"; - -const router = Router(); - -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({ - where: { 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({ where: { id: req.user_id } }); - body.image = (await handleFile(`/emojis/${id}`, body.image)) as string; - - const emoji = await Emoji.create({ - id: id, - guild_id: guild_id, - ...body, - require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not - 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({ where: { 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 Emoji.create({ - ...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({ where: { 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({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); - - res.sendStatus(204); - }, -); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts deleted file mode 100644 index 79c20678..00000000 --- a/src/api/routes/guilds/#guild_id/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Request, Response, Router } from "express"; -import { - DiscordApiErrors, - emitEvent, - getPermission, - getRights, - Guild, - GuildUpdateEvent, - handleFile, - Member, - GuildUpdateSchema, - FosscordApiErrors, -} from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; - -const router = Router(); - -router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - - const [guild, member] = await Promise.all([ - Guild.findOneOrFail({ where: { id: guild_id } }), - Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }), - ]); - if (!member) - throw new HTTPError( - "You are not a member of the guild you are trying to access", - 401, - ); - - // @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_GUILDS", - ); - - var guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - relations: ["emojis", "roles", "stickers"], - }); - - // TODO: guild update check image - - if (body.icon && body.icon != guild.icon) - body.icon = await handleFile(`/icons/${guild_id}`, body.icon); - - if (body.banner && body.banner !== guild.banner) - body.banner = await handleFile(`/banners/${guild_id}`, body.banner); - - if (body.splash && body.splash !== guild.splash) - body.splash = await handleFile( - `/splashes/${guild_id}`, - body.splash, - ); - - if ( - body.discovery_splash && - body.discovery_splash !== guild.discovery_splash - ) - body.discovery_splash = await handleFile( - `/discovery-splashes/${guild_id}`, - body.discovery_splash, - ); - - if (body.features) { - const diff = guild.features - .filter((x) => !body.features?.includes(x)) - .concat( - body.features.filter((x) => !guild.features.includes(x)), - ); - - // TODO move these - const MUTABLE_FEATURES = [ - "COMMUNITY", - "INVITES_DISABLED", - "DISCOVERABLE", - ]; - - for (var feature of diff) { - if (MUTABLE_FEATURES.includes(feature)) continue; - - throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams( - feature, - ); - } - - // for some reason, they don't update in the assign. - guild.features = body.features; - } - - // 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/src/api/routes/guilds/#guild_id/integrations.ts b/src/api/routes/guilds/#guild_id/integrations.ts deleted file mode 100644 index a8e78062..00000000 --- a/src/api/routes/guilds/#guild_id/integrations.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -const router = Router(); - -//TODO: implement integrations list -router.get("/", route({}), async (req: Request, res: Response) => { - res.json([]); -}); -export default router; diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts deleted file mode 100644 index 4d033e9c..00000000 --- a/src/api/routes/guilds/#guild_id/invites.ts +++ /dev/null @@ -1,22 +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/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts deleted file mode 100644 index c2f946b2..00000000 --- a/src/api/routes/guilds/#guild_id/member-verification.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Router, Request, Response } from "express"; -import { route } from "@fosscord/api"; -const router = Router(); - -router.get("/", route({}), async (req: Request, res: Response) => { - // TODO: member verification - - res.status(404).json({ - message: "Unknown Guild Member Verification Form", - code: 10068, - }); -}); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts deleted file mode 100644 index 0fcdd57c..00000000 --- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Request, Response, Router } from "express"; -import { - Member, - getPermission, - getRights, - Role, - GuildMemberUpdateEvent, - emitEvent, - Sticker, - Emoji, - Guild, - handleFile, - MemberChangeSchema, -} from "@fosscord/util"; -import { route } from "@fosscord/api"; - -const router = Router(); - -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({ - where: { 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; - - let 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({ - where: { guild_id: guild_id, name: "@everyone", position: 0 }, - }); - - if (body.avatar) - body.avatar = await handleFile( - `/guilds/${guild_id}/users/${member_id}/avatars`, - body.avatar as string, - ); - - member.assign(body); - - if ("roles" in body) { - permission.hasThrow("MANAGE_ROLES"); - - body.roles = body.roles || []; - body.roles.filter((x) => !!x); - - if (body.roles.indexOf(everyone.id) === -1) - body.roles.push(everyone.id); - member.roles = body.roles.map((x) => Role.create({ 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 { guild_id, member_id } = req.params; - const permission = await getPermission(req.user_id, guild_id); - const rights = await getRights(req.user_id); - 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/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts deleted file mode 100644 index 20443821..00000000 --- a/src/api/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(); - -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/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts deleted file mode 100644 index c0383912..00000000 --- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ /dev/null @@ -1,29 +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/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts deleted file mode 100644 index b516b9e9..00000000 --- a/src/api/routes/guilds/#guild_id/members/index.ts +++ /dev/null @@ -1,32 +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/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts deleted file mode 100644 index 88488871..00000000 --- a/src/api/routes/guilds/#guild_id/messages/search.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { Request, Response, Router } from "express"; -import { route } from "@fosscord/api"; -import { getPermission, FieldErrors, Message, Channel } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { FindManyOptions, In, Like } from "typeorm"; - -const router: Router = Router(); - -router.get("/", route({}), async (req: Request, res: Response) => { - const { - channel_id, - content, - include_nsfw, // TODO - offset, - sort_order, - sort_by, // TODO: Handle 'relevance' - limit, - author_id, - } = req.query; - - const parsedLimit = Number(limit) || 50; - if (parsedLimit < 1 || parsedLimit > 100) - throw new HTTPError("limit must be between 1 and 100", 422); - - if (sort_order) { - if ( - typeof sort_order != "string" || - ["desc", "asc"].indexOf(sort_order) == -1 - ) - throw FieldErrors({ - sort_order: { - message: "Value must be one of ('desc', 'asc').", - code: "BASE_TYPE_CHOICES", - }, - }); // todo this is wrong - } - - const permissions = await getPermission( - req.user_id, - req.params.guild_id, - channel_id as string | undefined, - ); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) - return res.json({ messages: [], total_results: 0 }); - - var query: FindManyOptions = { - order: { - timestamp: sort_order - ? (sort_order.toUpperCase() as "ASC" | "DESC") - : "DESC", - }, - take: parsedLimit || 0, - where: { - guild: { - id: req.params.guild_id, - }, - }, - relations: [ - "author", - "webhook", - "application", - "mentions", - "mention_roles", - "mention_channels", - "sticker_items", - "attachments", - ], - skip: offset ? Number(offset) : 0, - }; - //@ts-ignore - if (channel_id) query.where!.channel = { id: channel_id }; - else { - // get all channel IDs that this user can access - const channels = await Channel.find({ - where: { guild_id: req.params.guild_id }, - select: ["id"], - }); - const ids = []; - - for (var channel of channels) { - const perm = await getPermission( - req.user_id, - req.params.guild_id, - channel.id, - ); - if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) - continue; - ids.push(channel.id); - } - - //@ts-ignore - query.where!.channel = { id: In(ids) }; - } - //@ts-ignore - if (author_id) query.where!.author = { id: author_id }; - //@ts-ignore - if (content) query.where!.content = Like(`%${content}%`); - - const messages: Message[] = await Message.find(query); - - const messagesDto = messages.map((x) => [ - { - id: x.id, - type: x.type, - content: x.content, - channel_id: x.channel_id, - author: { - id: x.author?.id, - username: x.author?.username, - avatar: x.author?.avatar, - avatar_decoration: null, - discriminator: x.author?.discriminator, - public_flags: x.author?.public_flags, - }, - attachments: x.attachments, - embeds: x.embeds, - mentions: x.mentions, - mention_roles: x.mention_roles, - pinned: x.pinned, - mention_everyone: x.mention_everyone, - tts: x.tts, - timestamp: x.timestamp, - edited_timestamp: x.edited_timestamp, - flags: x.flags, - components: x.components, - hit: true, - }, - ]); - - return res.json({ - messages: messagesDto, - total_results: messages.length, - }); -}); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/premium.ts b/src/api/routes/guilds/#guild_id/premium.ts deleted file mode 100644 index 75361ac6..00000000 --- a/src/api/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/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts deleted file mode 100644 index 20a7fa95..00000000 --- a/src/api/routes/guilds/#guild_id/profile/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { route } from "@fosscord/api"; -import { - emitEvent, - GuildMemberUpdateEvent, - handleFile, - Member, - MemberChangeProfileSchema, - OrmUtils, -} from "@fosscord/util"; -import { Request, Response, Router } from "express"; - -const router = Router(); - -router.patch( - "/:member_id", - route({ body: "MemberChangeProfileSchema" }), - 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 MemberChangeProfileSchema; - - let member = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id }, - relations: ["roles", "user"], - }); - - if (body.banner) - body.banner = await handleFile( - `/guilds/${guild_id}/users/${req.user_id}/avatars`, - body.banner as string, - ); - - member = await OrmUtils.mergeDeep(member, body); - - await member.save(); - - // 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); - }, -); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts deleted file mode 100644 index 8089ad84..00000000 --- a/src/api/routes/guilds/#guild_id/prune.ts +++ /dev/null @@ -1,106 +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()), - }, - { - guild_id, - last_message_id: IsNull(), - }, - ], - relations: ["roles"], - }); - 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({ - where: { id: user_id, guild_id }, - relations: ["roles"], - }); - const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); - - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - - 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 }); -}); - -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/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts deleted file mode 100644 index 0b275ea4..00000000 --- a/src/api/routes/guilds/#guild_id/regions.ts +++ /dev/null @@ -1,20 +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({ where: { id: guild_id } }); - //TODO we should use an enum for guild's features and not hardcoded strings - return res.json( - await getVoiceRegions( - getIpAdress(req), - guild.features.includes("VIP_REGIONS"), - ), - ); -}); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts deleted file mode 100644 index 84648703..00000000 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Router, Request, Response } from "express"; -import { - Role, - Member, - GuildRoleUpdateEvent, - GuildRoleDeleteEvent, - emitEvent, - handleFile, - RoleModifySchema, -} from "@fosscord/util"; -import { route } from "@fosscord/api"; -import { HTTPError } from "lambert-server"; - -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({ where: { 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.length) - body.icon = await handleFile( - `/role-icons/${role_id}`, - body.icon as string, - ); - else body.icon = undefined; - - const role = await Role.findOneOrFail({ - where: { id: role_id, guild: { id: guild_id } }, - }); - role.assign({ - ...body, - 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/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts deleted file mode 100644 index 4cd47cf3..00000000 --- a/src/api/routes/guilds/#guild_id/roles/index.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Request, Response, Router } from "express"; -import { - Role, - getPermission, - Member, - GuildRoleCreateEvent, - GuildRoleUpdateEvent, - emitEvent, - Config, - DiscordApiErrors, - RoleModifySchema, - RolePositionUpdateSchema, - Snowflake, -} from "@fosscord/util"; -import { route } from "@fosscord/api"; -import { Not } from "typeorm"; - -const router: Router = Router(); - -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({ where: { 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({ where: { guild_id } }); - const { maxRoles } = Config.get().limits.guild; - - if (role_count > maxRoles) - throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); - - const role = Role.create({ - // values before ...body are default and can be overriden - position: 1, - 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: undefined, - unicode_emoji: undefined, - id: Snowflake.generate(), - }); - - await Promise.all([ - role.save(), - // Move all existing roles up one position, to accommodate the new role - Role.createQueryBuilder("roles") - .where({ - guild: { id: guild_id }, - name: Not("@everyone"), - id: Not(role.id), - }) - .update({ position: () => "position + 1" }) - .execute(), - 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/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts deleted file mode 100644 index 3b1f5f8e..00000000 --- a/src/api/routes/guilds/#guild_id/stickers.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - emitEvent, - GuildStickersUpdateEvent, - Member, - Snowflake, - Sticker, - StickerFormatType, - StickerType, - uploadFile, - ModifyGuildStickerSchema, -} 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({ where: { 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([ - Sticker.create({ - ...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({ where: { guild_id, id: sticker_id } }), - ); -}); - -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 Sticker.create({ - ...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({ where: { 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/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts deleted file mode 100644 index 3b5eddaa..00000000 --- a/src/api/routes/guilds/#guild_id/templates.ts +++ /dev/null @@ -1,116 +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", -]; - -router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - - var templates = await Template.find({ - where: { 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({ - where: { id: guild_id }, - }).catch((e) => {}); - if (exists) throw new HTTPError("Template already exists", 400); - - const template = await Template.create({ - ...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 Template.create({ - 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 Template.create({ - code, - name: name, - description: description, - source_guild_id: guild_id, - }).save(); - - res.json(template); - }, -); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts deleted file mode 100644 index 9a96b066..00000000 --- a/src/api/routes/guilds/#guild_id/vanity-url.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - Channel, - ChannelType, - Guild, - Invite, - VanityUrlSchema, -} 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({ where: { 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 })), - ); - } - }, -); - -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({ where: { id: guild_id } }); - if (!guild.features.includes("VANITY_URL")) - throw new HTTPError("Your guild doesn't support vanity urls"); - - if (!code || code.length === 0) - throw new HTTPError("Code cannot be null or empty"); - - const invite = await Invite.findOne({ where: { code } }); - if (invite) throw new HTTPError("Invite already exists"); - - const { id } = await Channel.findOneOrFail({ - where: { guild_id, type: ChannelType.GUILD_TEXT }, - }); - - await Invite.create({ - 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/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts deleted file mode 100644 index af03a07e..00000000 --- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - Channel, - ChannelType, - DiscordApiErrors, - emitEvent, - getPermission, - VoiceState, - VoiceStateUpdateEvent, - VoiceStateUpdateSchema, -} 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 - -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({ - where: { - guild_id, - channel_id: body.channel_id, - user_id, - }, - }); - if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - - voice_state.assign(body); - const channel = await Channel.findOneOrFail({ - where: { 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/src/api/routes/guilds/#guild_id/webhooks.ts b/src/api/routes/guilds/#guild_id/webhooks.ts deleted file mode 100644 index 9c4e8a8d..00000000 --- a/src/api/routes/guilds/#guild_id/webhooks.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -const router = Router(); - -//TODO: implement webhooks -router.get("/", route({}), async (req: Request, res: Response) => { - res.json([]); -}); -export default router; diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts deleted file mode 100644 index 80ab138b..00000000 --- a/src/api/routes/guilds/#guild_id/welcome-screen.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Request, Response, Router } from "express"; -import { Guild, Member, GuildUpdateWelcomeScreenSchema } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; - -const router: Router = Router(); - -router.get("/", route({}), async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - - const guild = await Guild.findOneOrFail({ where: { 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({ where: { id: guild_id } }); - - if (!guild.welcome_screen.enabled) - throw new HTTPError("Welcome screen disabled", 400); - if (body.welcome_channels) - guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid - if (body.description) - guild.welcome_screen.description = body.description; - if (body.enabled != null) guild.welcome_screen.enabled = body.enabled; - - await guild.save(); - - res.sendStatus(204); - }, -); - -export default router; diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts deleted file mode 100644 index 2c3124a2..00000000 --- a/src/api/routes/guilds/#guild_id/widget.json.ts +++ /dev/null @@ -1,97 +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({ where: { id: guild_id } }); - if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); - - // Fetch existing widget invite for widget channel - var invite = await Invite.findOne({ - where: { 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()); - - invite = await Invite.create({ - 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, - }).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({ where: { 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/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts deleted file mode 100644 index eaec8f07..00000000 --- a/src/api/routes/guilds/#guild_id/widget.png.ts +++ /dev/null @@ -1,179 +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({ where: { 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/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts deleted file mode 100644 index 108339e1..00000000 --- a/src/api/routes/guilds/#guild_id/widget.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Request, Response, Router } from "express"; -import { Guild, WidgetModifySchema } from "@fosscord/util"; -import { route } from "@fosscord/api"; - -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({ where: { 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/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts deleted file mode 100644 index 69575aea..00000000 --- a/src/api/routes/guilds/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Router, Request, Response } from "express"; -import { - Role, - Guild, - Config, - getRights, - Member, - DiscordApiErrors, - GuildCreateSchema, -} from "@fosscord/util"; -import { route } from "@fosscord/api"; - -const router: Router = Router(); - -//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({ where: { id: req.user_id } }); - const rights = await getRights(req.user_id); - if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) { - throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); - } - - 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/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts deleted file mode 100644 index 240bf074..00000000 --- a/src/api/routes/guilds/templates/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Request, Response, Router } from "express"; -import { - Template, - Guild, - Role, - Snowflake, - Config, - Member, - GuildTemplateCreateSchema, -} from "@fosscord/util"; -import { route } from "@fosscord/api"; -import { DiscordApiErrors } from "@fosscord/util"; -import fetch from "node-fetch"; -const router: Router = Router(); - -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({ where: { 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({ where: { id: req.user_id } }); - if (guild_count >= maxGuilds) { - throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); - } - - const template = await Template.findOneOrFail({ - where: { code: code }, - }); - - const guild_id = Snowflake.generate(); - - const [guild, role] = await Promise.all([ - Guild.create({ - ...body, - ...template.serialized_source_guild, - id: guild_id, - owner_id: req.user_id, - }).save(), - Role.create({ - id: guild_id, - guild_id: guild_id, - color: 0, - hoist: false, - managed: true, - mentionable: true, - name: "@everyone", - permissions: BigInt("2251804225").toString(), // TODO: where did this come from? - position: 0, - }).save(), - ]); - - await Member.addToGuild(req.user_id, guild_id); - - res.status(201).json({ id: guild.id }); - }, -); - -export default router; -- cgit 1.5.1