diff options
author | Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> | 2021-09-12 23:28:56 +0200 |
---|---|---|
committer | Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> | 2021-09-12 23:28:56 +0200 |
commit | 6ba9c834bd2d5f4fac07e3fb9ded2625ce3c44ad (patch) | |
tree | 27c5de63307dc170b0dfa02140b14a306ed55514 /api/src/routes/guilds | |
parent | :construction: :sparkles: new body parser (bans route) (diff) | |
download | server-6ba9c834bd2d5f4fac07e3fb9ded2625ce3c44ad.tar.xz |
:sparkles: #307 done
Diffstat (limited to 'api/src/routes/guilds')
21 files changed, 251 insertions, 210 deletions
diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 41a97b6a..c7fda9ad 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -2,7 +2,11 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress, check, route } from "@fosscord/api"; -import { BanCreateSchema } from "@fosscord/api/schema/Ban"; + +export interface BanCreateSchema { + delete_message_days?: string; + reason?: string; +} const router: Router = Router(); router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { @@ -27,7 +31,7 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER const banned_user = await User.getPublicUser(banned_user_id); if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); - if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); const ban = new Ban({ user_id: banned_user_id, diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index faeecb76..e21327d1 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,9 +1,8 @@ import { Router, Response, Request } from "express"; import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { ChannelModifySchema } from "../../../schema/Channel"; - -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; +import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); router.get("/", async (req: Request, res: Response) => { @@ -13,10 +12,7 @@ router.get("/", async (req: Request, res: Response) => { res.json(channels); }); -// TODO: check if channel type is permitted -// TODO: check if parent_id exists - -router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) => { +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; @@ -26,45 +22,39 @@ router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) res.status(201).json(channel); }); -// TODO: check if parent_id exists -router.patch( - "/", - check([{ id: String, $position: Number, $lock_permissions: Boolean, $parent_id: String }]), - async (req: Request, res: Response) => { - // changes guild channel position - const { guild_id } = req.params; - const body = req.body as { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; - - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_CHANNELS"); - - await Promise.all([ - body.map(async (x) => { - if (!x.position && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); +export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; - const opts: any = {}; - if (x.position) 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; - } +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 && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); + + const opts: any = {}; + if (x.position) opts.position = x.position; + + if (x.parent_id) { + opts.parent_id = x.parent_id; + const parent_channel = await Channel.findOneOrFail({ + where: { id: x.parent_id, guild_id }, + select: ["permission_overwrites"] + }); + if (x.lock_permissions) { + opts.permission_overwrites = parent_channel.permission_overwrites; } + } - await Channel.update({ guild_id, id: x.id }, opts); - const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); + await Channel.update({ guild_id, id: x.id }, opts); + const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); - await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); - }) - ]); + await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); + }) + ]); - res.sendStatus(204); - } -); + res.sendStatus(204); +}); export default router; diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index bbbd1fa4..7c3c5530 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -1,12 +1,13 @@ 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("/", async (req: Request, res: Response) => { +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"] }); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 244900ec..690d4103 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,23 +1,36 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { GuildUpdateSchema } from "../../../schema/Guild"; - -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; +import { GuildCreateSchema } from "../index"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface GuildUpdateSchema extends Omit<GuildCreateSchema, "channels"> { + banner?: string; + splash?: string; + description?: string; + features?: string[]; + verification_level?: number; + default_message_notifications?: number; + system_channel_flags?: number; + explicit_content_filter?: number; + public_updates_channel_id?: string; + afk_timeout?: number; + afk_channel_id?: string; + preferred_locale?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const [guild, member_count, member] = await Promise.all([ + const [guild, member] = await Promise.all([ Guild.findOneOrFail({ id: guild_id }), - Member.count({ guild_id: guild_id, id: req.user_id }), - Member.findOneOrFail({ id: req.user_id }) + Member.findOne({ guild_id: guild_id, id: req.user_id }) ]); - if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + 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; @@ -25,14 +38,11 @@ router.get("/", async (req: Request, res: Response) => { return res.json(guild); }); -router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const body = req.body as GuildUpdateSchema; const { guild_id } = req.params; // TODO: guild update check image - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts index 39a934ee..b7534e31 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/api/src/routes/guilds/#guild_id/invites.ts @@ -1,14 +1,12 @@ import { getPermission, Invite, PublicInviteRelation } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const permissions = await getPermission(req.user_id, guild_id); - permissions.hasThrow("MANAGE_GUILD"); - const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); return res.json(invites); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 8b04a508..1708b7eb 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,23 +1,15 @@ import { Request, Response, Router } from "express"; -import { - Guild, - Member, - User, - GuildMemberAddEvent, - getPermission, - PermissionResolvable, - Role, - GuildMemberUpdateEvent, - emitEvent -} from "@fosscord/util"; +import { Member, getPermission, Role, GuildMemberUpdateEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { MemberChangeSchema } from "@fosscord/api/schema/Member"; -import { In } from "typeorm"; +import { check, route } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface MemberChangeSchema { + roles?: string[]; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); @@ -26,8 +18,9 @@ router.get("/", async (req: Request, res: Response) => { return res.json(member); }); -router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; +router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => { + let { guild_id, member_id } = req.params; + if (member_id === "@me") member_id = req.user_id; const body = req.body as MemberChangeSchema; const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); @@ -39,7 +32,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) } await member.save(); - // do not use promise.all as we have to first write to db before emitting the event + // 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, @@ -49,7 +42,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) res.json(member); }); -router.put("/", async (req: Request, res: Response) => { +router.put("/", route({}), async (req: Request, res: Response) => { let { guild_id, member_id } = req.params; if (member_id === "@me") member_id = req.user_id; @@ -59,12 +52,9 @@ router.put("/", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("KICK_MEMBERS"); - await Member.removeFromGuild(member_id, guild_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts index 3f2975e6..27f7f65d 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -1,11 +1,14 @@ import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; -import { check } from "lambert-server"; -import { MemberNickChangeSchema } from "../../../../../schema/Member"; const router = Router(); -router.patch("/", check(MemberNickChangeSchema), async (req: Request, res: Response) => { +export interface MemberNickChangeSchema { + nick: string; +} + +router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => { var { guild_id, member_id } = req.params; var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; if (member_id === "@me") { diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index cb9bad9a..ae10be82 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -1,24 +1,19 @@ import { getPermission, Member } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); -router.delete("/:member_id/roles/:role_id", async (req: Request, res: Response) => { +router.delete("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); -router.put("/:member_id/roles/:role_id", async (req: Request, res: Response) => { +router.put("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - await Member.addRole(member_id, guild_id, role_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 198d6946..335f21c7 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,13 +1,15 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length } from "@fosscord/api"; +import { instanceOf, Length, route } from "@fosscord/api"; import { MoreThan } from "typeorm"; const router = Router(); // TODO: not allowed for user -> only allowed for bots with privileged intents // TODO: send over websocket -router.get("/", async (req: Request, res: Response) => { +// TODO: check for GUILD_MEMBERS intent + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); await Member.IsInGuildOrFail(req.user_id, guild_id); diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts index 86208b79..75d24fd1 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/api/src/routes/guilds/#guild_id/regions.ts @@ -1,11 +1,11 @@ import { Config, Guild, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { getVoiceRegions } from "@fosscord/api"; +import { getVoiceRegions, route } from "@fosscord/api"; import { getIpAdress } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); //TODO we should use an enum for guild's features and not hardcoded strings diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 76dd47c5..6b2902d9 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -2,23 +2,34 @@ import { Request, Response, Router } from "express"; import { Role, getPermission, - Snowflake, Member, GuildRoleCreateEvent, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, - Config + Config, + DiscordApiErrors } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; -import { RoleModifySchema, RolePositionUpdateSchema } from "../../../schema/Roles"; -import { DiscordApiErrors } from "@fosscord/util"; +import { check, route } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); +export interface RoleModifySchema { + name?: string; + permissions?: bigint; + color?: number; + hoist?: boolean; // whether the role should be displayed separately in the sidebar + mentionable?: boolean; // whether the role should be mentionable + position?: number; +} + +export type RolePositionUpdateSchema = { + id: string; + position: number; +}[]; + router.get("/", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; @@ -29,13 +40,10 @@ router.get("/", async (req: Request, res: Response) => { return res.json(roles); }); -router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => { +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 perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - const role_count = await Role.count({ guild_id }); const { maxRoles } = Config.get().limits.guild; @@ -50,7 +58,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => ...body, guild_id: guild_id, managed: false, - permissions: String(perms.bitfield & (body.permissions || 0n)), + permissions: String(req.permission!.bitfield & (body.permissions || 0n)), tags: undefined }); @@ -69,14 +77,11 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => res.json(role); }); -router.delete("/:role_id", async (req: Request, res: Response) => { +router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const { role_id } = req.params; if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); - const permissions = await getPermission(req.user_id, guild_id); - permissions.hasThrow("MANAGE_ROLES"); - await Promise.all([ Role.delete({ id: role_id, @@ -97,14 +102,11 @@ router.delete("/:role_id", async (req: Request, res: Response) => { // TODO: check role hierarchy -router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Response) => { +router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { role_id, guild_id } = req.params; const body = req.body as RoleModifySchema; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - - const role = new Role({ ...body, id: role_id, guild_id, permissions: String(perms.bitfield & (body.permissions || 0n)) }); + const role = new Role({ ...body, id: role_id, guild_id, permissions: String(req.permission!.bitfield & (body.permissions || 0n)) }); await Promise.all([ role.save(), @@ -121,7 +123,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res res.json(role); }); -router.patch("/", check(RolePositionUpdateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const body = req.body as RolePositionUpdateSchema; diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index e9304e11..5179e761 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { Guild, getPermission, Template } from "@fosscord/util"; +import { Guild, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { generateCode } from "@fosscord/api"; const router: Router = Router(); @@ -24,7 +23,17 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "icon" ]; -router.get("/", async (req: Request, res: Response) => { +export interface TemplateCreateSchema { + name: string; + description?: string; +} + +export interface TemplateModifySchema { + name: string; + description?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; var templates = await Template.find({ source_guild_id: guild_id }); @@ -32,12 +41,9 @@ router.get("/", async (req: Request, res: Response) => { return res.json(templates); }); -router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); @@ -54,44 +60,31 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response res.json(template); }); -router.delete("/:code", async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const { code } = req.params; - - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); +router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + const { code, guild_id } = req.params; const template = await Template.delete({ - code + code, + source_guild_id: guild_id }); res.json(template); }); -router.put("/:code", async (req: Request, res: Response) => { - // synchronizes the 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 perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const template = await new Template({ code, serialized_source_guild: guild }).save(); res.json(template); }); -router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => { - // updates the template description - const { guild_id } = req.params; - const { code } = req.params; +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 perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - - const template = await new Template({ code, name: name, description: description }).save(); + const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save(); res.json(template); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index f1887cc0..9c0989cc 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,35 +1,37 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; -import { check, Length } from "@fosscord/api"; +import { check, Length, route } from "@fosscord/api"; const router = Router(); const InviteRegex = /\W/g; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["vanity_url"] }); if (!guild.vanity_url) return res.json({ code: null }); return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses }); }); +export interface VanityUrlSchema { + /** + * @minLength 1 + * @maxLength 20 + */ + code?: string; +} + // TODO: check if guild is elgible for vanity url -router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Request, res: Response) => { +router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const code = req.body.code.replace(InviteRegex); + const body = req.body as VanityUrlSchema; + const code = body.code?.replace(InviteRegex, ""); await Invite.findOneOrFail({ code }); const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_GUILD"); - const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); guild.vanity_url_code = code; diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 447e15c1..3d76938b 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,14 +1,59 @@ -import { check } from "@fosscord/api"; -import { VoiceStateUpdateSchema } from "../../../../../schema"; +import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; +import { check, route } from "@fosscord/api"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "@fosscord/api"; const router = Router(); +//TODO need more testing when community guild and voice stage channel are working -router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { +export interface VoiceStateUpdateSchema { + channel_id: string; + guild_id?: string; + suppress?: boolean; + request_to_speak_timestamp?: Date; + self_mute?: boolean; + self_deaf?: boolean; + self_video?: boolean; +} + +router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; - const { guild_id, user_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id, user_id); + var { guild_id, user_id } = req.params; + if (user_id === "@me") user_id = req.user_id; + + const perms = await getPermission(req.user_id, guild_id, body.channel_id); + + /* + From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state + You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself. + You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. + */ + if (body.suppress && user_id !== req.user_id) { + perms.hasThrow("MUTE_MEMBERS"); + } + if (!body.suppress) body.request_to_speak_timestamp = new Date(); + if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); + + const voice_state = await VoiceState.findOne({ + guild_id, + channel_id: body.channel_id, + user_id + }); + if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; + + voice_state.assign(body); + const channel = await Channel.findOneOrFail({ guild_id, id: body.channel_id }); + if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { + throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; + } + + await Promise.all([ + voice_state.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voice_state, + guild_id + } as VoiceStateUpdateEvent) + ]); return res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts deleted file mode 100644 index b637ff66..00000000 --- a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { check } from "@fosscord/api"; -import { VoiceStateUpdateSchema } from "../../../../../schema"; -import { Request, Response, Router } from "express"; -import { updateVoiceState } from "@fosscord/api"; - -const router = Router(); - -router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { - const body = req.body as VoiceStateUpdateSchema; - const { guild_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id); - return res.sendStatus(204); -}); - -export default router; diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index 7ca49b4e..7141f17e 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -1,31 +1,36 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; -import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface GuildUpdateWelcomeScreenSchema { + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name: string; + }[]; + enabled?: boolean; + description?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const guild = await Guild.findOneOrFail({ id: guild_id }); - await Member.IsInGuildOrFail(req.user_id, guild_id); res.json(guild.welcome_screen); }); -router.patch("/", check(GuildUpdateWelcomeScreenSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as GuildUpdateWelcomeScreenSchema; const guild = await Guild.findOneOrFail({ id: guild_id }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - 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; diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index f871fac8..c31519fa 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { random } from "@fosscord/api"; +import { random, route } from "@fosscord/api"; const router: Router = Router(); @@ -14,7 +14,7 @@ const router: Router = Router(); // 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("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/api/src/routes/guilds/#guild_id/widget.png.ts index 89b31153..4c82b740 100644 --- a/api/src/routes/guilds/#guild_id/widget.png.ts +++ b/api/src/routes/guilds/#guild_id/widget.png.ts @@ -1,6 +1,7 @@ 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"; @@ -10,7 +11,7 @@ const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-image // TODO: Cache the response -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index d9ce817e..c8caae14 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,31 +1,29 @@ import { Request, Response, Router } from "express"; import { getPermission, Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { WidgetModifySchema } from "../../../schema/Widget"; +import { check, route } from "@fosscord/api"; + +export interface WidgetModifySchema { + enabled: boolean; // whether the widget is enabled + channel_id: string; // the widget channel id +} const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-settings -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ id: guild_id }); return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null }); }); // https://discord.com/developers/docs/resources/guild#modify-guild-widget -router.patch("/", check(WidgetModifySchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const body = req.body as WidgetModifySchema; const { guild_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - 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 diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index a1b199e7..ba951f96 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,15 +1,28 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { GuildCreateSchema } from "../../schema/Guild"; +import { check, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; +import { ChannelModifySchema } from "../channels/#channel_id"; const router: Router = Router(); +export interface GuildCreateSchema { + /** + * @maxLength 100 + */ + name: string; + region?: string; + icon?: string; + channels?: ChannelModifySchema[]; + guild_template_code?: string; + system_channel_id?: string; + rules_channel_id?: string; +} + //TODO: create default channel -router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index 1d0f2716..d7a42044 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -2,11 +2,15 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; -router.get("/:code", async (req: Request, res: Response) => { +export interface GuildTemplateCreateSchema { + name: string; + avatar?: string; +} + +router.get("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const template = await Template.findOneOrFail({ code: code }); @@ -14,7 +18,7 @@ router.get("/:code", async (req: Request, res: Response) => { res.json(template); }); -router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => { +router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => { const { code } = req.params; const body = req.body as GuildTemplateCreateSchema; |