summary refs log tree commit diff
path: root/api/src/routes/guilds
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/routes/guilds')
-rw-r--r--api/src/routes/guilds/#guild_id/bans.ts71
-rw-r--r--api/src/routes/guilds/#guild_id/delete.ts19
-rw-r--r--api/src/routes/guilds/#guild_id/index.ts39
-rw-r--r--api/src/routes/guilds/#guild_id/invites.ts2
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/index.ts27
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/nick.ts5
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts7
-rw-r--r--api/src/routes/guilds/#guild_id/members/index.ts21
-rw-r--r--api/src/routes/guilds/#guild_id/roles.ts68
-rw-r--r--api/src/routes/guilds/#guild_id/vanity-url.ts38
-rw-r--r--api/src/routes/guilds/#guild_id/welcome_screen.ts28
-rw-r--r--api/src/routes/guilds/#guild_id/widget.json.ts95
-rw-r--r--api/src/routes/guilds/index.ts17
-rw-r--r--api/src/routes/guilds/templates/index.ts25
14 files changed, 178 insertions, 284 deletions
diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts
index cbc0b0fa..b84a68a7 100644
--- a/api/src/routes/guilds/#guild_id/bans.ts
+++ b/api/src/routes/guilds/#guild_id/bans.ts
@@ -1,22 +1,17 @@
 import { Request, Response, Router } from "express";
-import { BanModel, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, toObject } from "@fosscord/util";
+import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { getIpAdress } from "../../../util/ipAddress";
 import { BanCreateSchema } from "../../../schema/Ban";
 
 import { check } from "../../../util/instanceOf";
-import { removeMember } from "../../../util/Member";
-import { getPublicUser } from "../../../util/User";
 
 const router: Router = Router();
 
 router.get("/", async (req: Request, res: Response) => {
 	const { guild_id } = req.params;
 
-	const guild = await Guild.exists({ id: guild_id });
-	if (!guild) throw new HTTPError("Guild not found", 404);
-
-	var bans = await Ban.find({ guild_id: guild_id }, { user_id: true, reason: true });
+	var bans = await Ban.find({ guild_id: guild_id });
 	return res.json(bans);
 });
 
@@ -32,29 +27,32 @@ router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Respon
 	const { guild_id } = req.params;
 	const banned_user_id = req.params.user_id;
 
-	const banned_user = await getPublicUser(banned_user_id);
+	const banned_user = await User.getPublicUser(banned_user_id);
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("BAN_MEMBERS");
 	if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400);
+	if (perms.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400);
 
-	await removeMember(banned_user_id, guild_id);
-
-	const ban = await new BanModel({
+	const ban = new Ban({
 		user_id: banned_user_id,
 		guild_id: guild_id,
 		ip: getIpAdress(req),
 		executor_id: req.user_id,
 		reason: req.body.reason // || otherwise empty
-	}).save();
+	});
 
-	await emitEvent({
-		event: "GUILD_BAN_ADD",
-		data: {
-			guild_id: guild_id,
-			user: banned_user
-		},
-		guild_id: guild_id
-	} as GuildBanAddEvent);
+	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);
 });
@@ -63,26 +61,25 @@ router.delete("/:user_id", async (req: Request, res: Response) => {
 	var { guild_id } = req.params;
 	var banned_user_id = req.params.user_id;
 
-	const banned_user = await getPublicUser(banned_user_id);
-	const guild = await Guild.exists({ id: guild_id });
-	if (!guild) throw new HTTPError("Guild not found", 404);
-
+	const banned_user = await User.getPublicUser(banned_user_id);
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("BAN_MEMBERS");
 
-	await Ban.deleteOne({
-		user_id: banned_user_id,
-		guild_id
-	});
-
-	await emitEvent({
-		event: "GUILD_BAN_REMOVE",
-		data: {
-			guild_id,
-			user: banned_user
-		},
-		guild_id
-	} as GuildBanRemoveEvent);
+	await Promise.all([
+		Ban.delete({
+			user_id: banned_user_id,
+			guild_id
+		}),
+
+		emitEvent({
+			event: "GUILD_BAN_REMOVE",
+			data: {
+				guild_id,
+				user: banned_user
+			},
+			guild_id
+		} as GuildBanRemoveEvent)
+	]);
 
 	return res.status(204).send();
 });
diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts
index a53271ce..043260e9 100644
--- a/api/src/routes/guilds/#guild_id/delete.ts
+++ b/api/src/routes/guilds/#guild_id/delete.ts
@@ -1,4 +1,4 @@
-import { Channel, emitEvent, EmojiModel, GuildDeleteEvent, Guild, InviteModel, Member, Message, Role, User } from "@fosscord/util";
+import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util";
 import { Router, Request, Response } from "express";
 import { HTTPError } from "lambert-server";
 
@@ -9,7 +9,7 @@ const router = Router();
 router.post("/", async (req: Request, res: Response) => {
 	var { guild_id } = req.params;
 
-	const guild = await Guild.findOneOrFail({ id: guild_id }, "owner_id");
+	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 emitEvent({
@@ -21,14 +21,13 @@ router.post("/", async (req: Request, res: Response) => {
 	} as GuildDeleteEvent);
 
 	await Promise.all([
-		Guild.deleteOne({ id: guild_id }),
-		User.updateMany({ guilds: guild_id }, { $pull: { guilds: guild_id } }),
-		Role.deleteMany({ guild_id }),
-		Channel.deleteMany({ guild_id }),
-		Emoji.deleteMany({ guild_id }),
-		Invite.deleteMany({ guild_id }),
-		Message.deleteMany({ guild_id }),
-		Member.deleteMany({ guild_id })
+		Guild.delete({ id: guild_id }),
+		Role.delete({ guild_id }),
+		Channel.delete({ guild_id }),
+		Emoji.delete({ guild_id }),
+		Invite.delete({ guild_id }),
+		Message.delete({ guild_id }),
+		Member.delete({ guild_id })
 	]);
 
 	return res.sendStatus(204);
diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts
index af9ea9d6..d205b164 100644
--- a/api/src/routes/guilds/#guild_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/index.ts
@@ -1,19 +1,5 @@
 import { Request, Response, Router } from "express";
-import {
-	Channel,
-	emitEvent,
-	EmojiModel,
-	getPermission,
-	GuildDeleteEvent,
-	Guild,
-	GuildUpdateEvent,
-	InviteModel,
-	Member,
-	Message,
-	Role,
-	toObject,
-	User
-} from "@fosscord/util";
+import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { GuildUpdateSchema } from "../../../schema/Guild";
 
@@ -26,9 +12,15 @@ const router = Router();
 router.get("/", async (req: Request, res: Response) => {
 	const { guild_id } = req.params;
 
-	const guild = await Guild.findOneOrFail({ id: guild_id }).populate({ path: "joined_at", match: { id: req.user_id } });
-	const member = await Member.exists({ 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);
+	const [guild, member_count, member] = await Promise.all([
+		Guild.findOneOrFail({ id: guild_id }),
+		Member.count({ guild_id: guild_id, id: req.user_id }),
+		Member.findOneOrFail(req.user_id)
+	]);
+	if (!member_count) 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.json(guild);
 });
@@ -45,15 +37,12 @@ router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response)
 	if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
 	if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
 
-	const guild = await Guild.findOneOrFailAndUpdate({ id: guild_id }, body, { new: true }).populate({
-		path: "joined_at",
-		match: { id: req.user_id }
-	});
-	const data = guild;
+	const guild = await Guild.findOneOrFail({ id: guild_id });
+	guild.assign(body);
 
-	emitEvent({ event: "GUILD_UPDATE", data: data, guild_id } as GuildUpdateEvent);
+	await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data: guild, guild_id } as GuildUpdateEvent)]);
 
-	return res.json(data);
+	return res.json(guild);
 });
 
 export default router;
diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts
index ca72cce8..1843b689 100644
--- a/api/src/routes/guilds/#guild_id/invites.ts
+++ b/api/src/routes/guilds/#guild_id/invites.ts
@@ -1,4 +1,4 @@
-import { getPermission, InviteModel, toObject } from "@fosscord/util";
+import { getPermission, Invite } from "@fosscord/util";
 import { Request, Response, Router } from "express";
 
 const router = Router();
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
index 1dacbdad..db29cd08 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
@@ -3,7 +3,6 @@ import {
 	Guild,
 	Member,
 	User,
-	toObject,
 	GuildMemberAddEvent,
 	getPermission,
 	PermissionResolvable,
@@ -12,15 +11,15 @@ import {
 	emitEvent
 } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
-import { addMember, isMember, removeMember } from "../../../../../util/Member";
 import { check } from "../../../../../util/instanceOf";
 import { MemberChangeSchema } from "../../../../../schema/Member";
+import { In } from "typeorm";
 
 const router = Router();
 
 router.get("/", async (req: Request, res: Response) => {
 	const { guild_id, member_id } = req.params;
-	await isMember(req.user_id, guild_id);
+	await Member.IsInGuildOrFail(req.user_id, guild_id);
 
 	const member = await Member.findOneOrFail({ id: member_id, guild_id });
 
@@ -31,18 +30,22 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response)
 	const { guild_id, member_id } = req.params;
 	const body = req.body as MemberChangeSchema;
 	if (body.roles) {
-		const roles = await Role.find({ id: { $in: body.roles } });
+		const roles = await Role.find({ id: In(body.roles) });
 		if (body.roles.length !== roles.length) throw new HTTPError("Roles not found", 404);
 		// TODO: check if user has permission to add role
 	}
 
-	const member = await Member.findOneOrFailAndUpdate({ id: member_id, guild_id }, body, { new: true });
+	const member = await Member.findOneOrFail({ id: member_id, guild_id });
+	member.assign(req.body);
 
-	await emitEvent({
-		event: "GUILD_MEMBER_UPDATE",
-		guild_id,
-		data: member
-	} as GuildMemberUpdateEvent);
+	Promise.all([
+		member.save(),
+		emitEvent({
+			event: "GUILD_MEMBER_UPDATE",
+			guild_id,
+			data: { ...member, roles: member.role_ids }
+		} as GuildMemberUpdateEvent)
+	]);
 
 	res.json(member);
 });
@@ -52,7 +55,7 @@ router.put("/", async (req: Request, res: Response) => {
 
 	throw new HTTPError("Maintenance: Currently you can't add a member", 403);
 	// TODO: only for oauth2 applications
-	await addMember(member_id, guild_id);
+	await Member.addToGuild(member_id, guild_id);
 	res.sendStatus(204);
 });
 
@@ -62,7 +65,7 @@ router.delete("/", async (req: Request, res: Response) => {
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("KICK_MEMBERS");
 
-	await removeMember(member_id, guild_id);
+	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 e4308364..3f2975e6 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,8 +1,7 @@
-import { getPermission, PermissionResolvable } from "@fosscord/util";
+import { getPermission, Member, PermissionResolvable } from "@fosscord/util";
 import { Request, Response, Router } from "express";
 import { check } from "lambert-server";
 import { MemberNickChangeSchema } from "../../../../../schema/Member";
-import { changeNickname } from "../../../../../util/Member";
 
 const router = Router();
 
@@ -17,7 +16,7 @@ router.patch("/", check(MemberNickChangeSchema), async (req: Request, res: Respo
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow(permissionString);
 
-	await changeNickname(member_id, guild_id, req.body.nick);
+	await Member.changeNickname(member_id, guild_id, req.body.nick);
 	res.status(200).send();
 });
 
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 fad0695e..cb9bad9a 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,6 +1,5 @@
-import { getPermission } from "@fosscord/util";
+import { getPermission, Member } from "@fosscord/util";
 import { Request, Response, Router } from "express";
-import { addRole, removeRole } from "../../../../../../../util/Member";
 
 const router = Router();
 
@@ -10,7 +9,7 @@ router.delete("/:member_id/roles/:role_id", async (req: Request, res: Response)
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("MANAGE_ROLES");
 
-	await removeRole(member_id, guild_id, role_id);
+	await Member.removeRole(member_id, guild_id, role_id);
 	res.sendStatus(204);
 });
 
@@ -20,7 +19,7 @@ router.put("/:member_id/roles/:role_id", async (req: Request, res: Response) =>
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("MANAGE_ROLES");
 
-	await addRole(member_id, guild_id, role_id);
+	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 656d3acd..0bfd71cb 100644
--- a/api/src/routes/guilds/#guild_id/members/index.ts
+++ b/api/src/routes/guilds/#guild_id/members/index.ts
@@ -1,8 +1,7 @@
 import { Request, Response, Router } from "express";
-import { Guild, Member, toObject } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { Guild, Member, PublicMemberProjection } from "@fosscord/util";
 import { instanceOf, Length } from "../../../../util/instanceOf";
-import { PublicMemberProjection, isMember } from "../../../../util/Member";
+import { MoreThan } from "typeorm";
 
 const router = Router();
 
@@ -11,7 +10,7 @@ const router = Router();
 router.get("/", async (req: Request, res: Response) => {
 	const { guild_id } = req.params;
 	const guild = await Guild.findOneOrFail({ id: guild_id });
-	await isMember(req.user_id, guild_id);
+	await Member.IsInGuildOrFail(req.user_id, guild_id);
 
 	try {
 		instanceOf({ $limit: new Length(Number, 1, 1000), $after: String }, req.query, {
@@ -23,12 +22,16 @@ router.get("/", async (req: Request, res: Response) => {
 		return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error });
 	}
 
-	// @ts-ignore
-	if (!req.query.limit) req.query.limit = 1;
-	const { limit, after } = (<unknown>req.query) as { limit: number; after: string };
-	const query = after ? { id: { $gt: after } } : {};
+	const { limit, after } = (<unknown>req.query) as { limit?: number; after?: string };
+	const query = after ? { id: MoreThan(after) } : {};
+
+	const members = await Member.find({
+		where: { guild_id, ...query },
+		select: PublicMemberProjection,
+		take: limit || 1,
+		order: { id: "ASC" }
+	});
 
-	var members = await Member.find({ guild_id, ...query }, PublicMemberProjection).limit(limit);
 	return res.json(members);
 });
 
diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts
index 5ebc0580..e9e777b9 100644
--- a/api/src/routes/guilds/#guild_id/roles.ts
+++ b/api/src/routes/guilds/#guild_id/roles.ts
@@ -1,10 +1,7 @@
 import { Request, Response, Router } from "express";
 import {
 	Role,
-	Guild,
 	getPermission,
-	toObject,
-	User,
 	Snowflake,
 	Member,
 	GuildRoleCreateEvent,
@@ -16,15 +13,13 @@ import { HTTPError } from "lambert-server";
 
 import { check } from "../../../util/instanceOf";
 import { RoleModifySchema } from "../../../schema/Roles";
-import { getPublicUser } from "../../../util/User";
-import { isMember } from "../../../util/Member";
 
 const router: Router = Router();
 
 router.get("/", async (req: Request, res: Response) => {
 	const guild_id = req.params.guild_id;
 
-	await isMember(req.user_id, guild_id);
+	await Member.IsInGuildOrFail(req.user_id, guild_id);
 
 	const roles = await Role.find({ guild_id: guild_id });
 
@@ -35,12 +30,8 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) =>
 	const guild_id = req.params.guild_id;
 	const body = req.body as RoleModifySchema;
 
-	const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true });
-	const user = await User.findOneOrFail({ id: req.user_id });
-
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("MANAGE_ROLES");
-	if (!body.name) throw new HTTPError("You need to specify a name");
 
 	const role = await new Role({
 		...body,
@@ -49,7 +40,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) =>
 		managed: false,
 		position: 0,
 		tags: null,
-		permissions: body.permissions || 0n
+		permissions: perms.bitfield & (body.permissions || 0n)
 	}).save();
 
 	await emitEvent({
@@ -72,19 +63,20 @@ router.delete("/:role_id", async (req: Request, res: Response) => {
 	const permissions = await getPermission(req.user_id, guild_id);
 	permissions.hasThrow("MANAGE_ROLES");
 
-	await Role.deleteOne({
-		id: role_id,
-		guild_id: guild_id
-	});
-
-	await emitEvent({
-		event: "GUILD_ROLE_DELETE",
-		guild_id,
-		data: {
+	await Promise.all([
+		Role.delete({
+			id: role_id,
+			guild_id: guild_id
+		}),
+		emitEvent({
+			event: "GUILD_ROLE_DELETE",
 			guild_id,
-			role_id
-		}
-	} as GuildRoleDeleteEvent);
+			data: {
+				guild_id,
+				role_id
+			}
+		} as GuildRoleDeleteEvent)
+	]);
 
 	res.sendStatus(204);
 });
@@ -96,30 +88,22 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res
 	const { role_id } = req.params;
 	const body = req.body as RoleModifySchema;
 
-	const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true });
-	const user = await User.findOneOrFail({ id: req.user_id });
-
 	const perms = await getPermission(req.user_id, guild_id);
 	perms.hasThrow("MANAGE_ROLES");
 
-	const role = await Role.findOneOrFailAndUpdate(
-		{
-			id: role_id,
-			guild_id: guild_id
-		},
-		// @ts-ignore
-		body,
-		{ new: true }
-	);
+	const role = new Role({ ...body, role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) });
 
-	await emitEvent({
-		event: "GUILD_ROLE_UPDATE",
-		guild_id,
-		data: {
+	await Promise.all([
+		role.save(),
+		emitEvent({
+			event: "GUILD_ROLE_UPDATE",
 			guild_id,
-			role
-		}
-	} as GuildRoleUpdateEvent);
+			data: {
+				guild_id,
+				role
+			}
+		} as GuildRoleUpdateEvent)
+	]);
 
 	res.json(role);
 });
diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts
index 335cea27..58940b42 100644
--- a/api/src/routes/guilds/#guild_id/vanity-url.ts
+++ b/api/src/routes/guilds/#guild_id/vanity-url.ts
@@ -1,8 +1,7 @@
-import { Channel, ChannelType, getPermission, Guild, InviteModel, trimSpecial } from "@fosscord/util";
+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 "../../../util/instanceOf";
-import { isMember } from "../../../util/Member";
 
 const router = Router();
 
@@ -14,42 +13,37 @@ router.get("/", async (req: Request, res: Response) => {
 	const permission = await getPermission(req.user_id, guild_id);
 	permission.hasThrow("MANAGE_GUILD");
 
-	const guild = await Guild.findOneOrFail({ id: guild_id });
-	if (!guild.vanity_url_code) return res.json({ code: null });
-	const { uses } = await Invite.findOneOrFail({ code: guild.vanity_url_code });
+	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 });
+	return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses });
 });
 
 // TODO: check if guild is elgible for vanity url
 router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Request, res: Response) => {
 	const { guild_id } = req.params;
-	var code = req.body.code.replace(InviteRegex);
-	if (!code) code = null;
+	const code = req.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, undefined, { guild });
+	const permission = await getPermission(req.user_id, guild_id);
 	permission.hasThrow("MANAGE_GUILD");
 
-	const alreadyExists = await Promise.all([
-		Guild.findOneOrFail({ vanity_url_code: code }).catch(() => null),
-		Invite.findOneOrFail({ code: code }).catch(() => null)
-	]);
-	if (alreadyExists.some((x) => x)) throw new HTTPError("Vanity url already exists", 400);
-
-	await Guild.update({ id: guild_id }, { vanity_url_code: code });
 	const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT });
-	await Invite.update(
-		{ code: guild.vanity_url_code },
-		{
+	guild.vanity_url_code = code;
+
+	Promise.all([
+		guild.save(),
+		Invite.delete({ code: guild.vanity_url_code }),
+		new Invite({
 			code: code,
 			uses: 0,
 			created_at: new Date(),
 			guild_id,
 			channel_id: id
-		},
-		{ upsert: true }
-	);
+		}).save()
+	]);
 
 	return res.json({ code: code });
 });
diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts
index b457efb6..defbcd40 100644
--- a/api/src/routes/guilds/#guild_id/welcome_screen.ts
+++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts
@@ -1,11 +1,9 @@
 import { Request, Response, Router } from "express";
-import { Guild, getPermission, toObject, Snowflake } from "@fosscord/util";
+import { Guild, getPermission, Snowflake, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 
 import { check } from "../../../util/instanceOf";
-import { isMember } from "../../../util/Member";
-import { GuildAddChannelToWelcomeScreenSchema } from "../../../schema/Guild";
-import { getPublicUser } from "../../../util/User";
+import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild";
 
 const router: Router = Router();
 
@@ -14,34 +12,24 @@ router.get("/", async (req: Request, res: Response) => {
 
 	const guild = await Guild.findOneOrFail({ id: guild_id });
 
-	await isMember(req.user_id, guild_id);
+	await Member.IsInGuildOrFail(req.user_id, guild_id);
 
 	res.json(guild.welcome_screen);
 });
 
-router.post("/", check(GuildAddChannelToWelcomeScreenSchema), async (req: Request, res: Response) => {
+router.patch("/", check(GuildUpdateWelcomeScreenSchema), async (req: Request, res: Response) => {
 	const guild_id = req.params.guild_id;
-	const body = req.body as GuildAddChannelToWelcomeScreenSchema;
+	const body = req.body as GuildUpdateWelcomeScreenSchema;
 
 	const guild = await Guild.findOneOrFail({ id: guild_id });
 
-	var channelObject = {
-		...body
-	};
-
 	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 (guild.welcome_screen.welcome_channels.some((channel) => channel.channel_id === body.channel_id))
-		throw new Error("Welcome Channel exists");
-
-	await Guild.findOneOrFailAndUpdate(
-		{
-			id: guild_id
-		},
-		{ $push: { "welcome_screen.welcome_channels": channelObject } }
-	);
+	if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
+	if (body.description) guild.welcome_screen.description = body.description;
+	if (body.enabled != null) guild.welcome_screen.enabled = body.enabled;
 
 	res.sendStatus(204);
 });
diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts
index 10bc3ac0..ae1f0599 100644
--- a/api/src/routes/guilds/#guild_id/widget.json.ts
+++ b/api/src/routes/guilds/#guild_id/widget.json.ts
@@ -1,5 +1,5 @@
 import { Request, Response, Router } from "express";
-import { Config, Permissions, Guild, InviteModel, Channel, Member } from "@fosscord/util";
+import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { random } from "../../../util/RandomInviteID";
 
@@ -21,7 +21,8 @@ router.get("/", async (req: Request, res: Response) => {
 	if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
 
 	// Fetch existing widget invite for widget channel
-	var invite = await Invite.findOneOrFail({ channel_id: guild.widget_channel_id, inviter_id: { $type: 10 } });
+	var invite = await Invite.findOne({ channel_id: guild.widget_channel_id });
+
 	if (guild.widget_channel_id && !invite) {
 		// Create invite for channel if none exists
 		// TODO: Refactor invite create code to a shared function
@@ -40,85 +41,29 @@ router.get("/", async (req: Request, res: Response) => {
 			inviter_id: null
 		};
 
-		invite = await new InviteModel(body).save();
+		invite = await new Invite(body).save();
 	}
 
 	// Fetch voice channels, and the @everyone permissions object
-	let channels: any[] = [];
-	await Channel.find({ guild_id: guild_id, type: 2 }, { permission_overwrites: { $elemMatch: { id: guild_id } } })
-		.select("id name position permission_overwrites")
-		.sort({ position: 1 })
-		.cursor()
-		.eachAsync((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
-				});
-			}
-		});
+	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: any[] = [];
-	await Member.find({ guild_id: guild_id })
-		.populate({ path: "user", select: { _id: 0, username: 1, avatar: 1, presence: 1 } })
-		.select("id user nick deaf mute")
-		.cursor()
-		.eachAsync((doc) => {
-			const status = doc.user?.presence?.status || "offline";
-			if (status == "offline") return;
-
-			let item = {};
-
-			item = {
-				...item,
-				id: null, // this is updated during the sort outside of the query
-				username: doc.nick || doc.user?.username,
-				discriminator: "0000", // intended (https://github.com/discord/discord-api-docs/issues/1287)
-				avatar: null, // intended, avatar_url below will return a unique guild + user url to the avatar
-				status: status
-			};
-
-			const activity = doc.user?.presence?.activities?.[0];
-			if (activity) {
-				item = {
-					...item,
-					game: { name: activity.name }
-				};
-			}
-
-			// TODO: If the member is in a voice channel, return extra widget details
-			// Extra fields returned include deaf, mute, self_deaf, self_mute, supress, and channel_id (voice channel connected to)
-			// Get this from VoiceState
-
-			// TODO: Implement a widget-avatar endpoint on the CDN, and implement logic here to request it
-			// Get unique avatar url for guild user, cdn to serve the actual avatar image on this url
-			/*
-		const avatar = doc.user?.avatar;
-		if (avatar) {
-			const CDN_HOST = Config.get().cdn.endpoint || "http://localhost:3003";
-			const avatar_url = "/widget-avatars/" + ;
-			item = {
-				...item,
-				avatar_url: avatar_url
-			}
-		}
-		*/
-
-			members.push(item);
-		});
-
-	// Sort members, and update ids (Unable to do under the mongoose query due to https://mongoosejs.com/docs/faq.html#populate_sort_order)
-	members = members.sort((first, second) => 0 - (first.username > second.username ? -1 : 1));
-	members.forEach((x, i) => {
-		x.id = i;
-	});
+	let members = await Member.find({ where: { guild_id: guild_id } });
 
 	// Construct object to respond with
 	const data = {
diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts
index 05be07d9..c158c7d4 100644
--- a/api/src/routes/guilds/index.ts
+++ b/api/src/routes/guilds/index.ts
@@ -1,10 +1,8 @@
 import { Router, Request, Response } from "express";
-import { Role, Guild, Snowflake, Guild, RoleDocument, Config } from "@fosscord/util";
+import { Role, Guild, Snowflake, Config, User, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { check } from "./../../util/instanceOf";
 import { GuildCreateSchema } from "../../schema/Guild";
-import { getPublicUser } from "../../util/User";
-import { addMember } from "../../util/Member";
 import { createChannel } from "../../util/Channel";
 
 const router: Router = Router();
@@ -15,14 +13,13 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
 	const body = req.body as GuildCreateSchema;
 
 	const { maxGuilds } = Config.get().limits.user;
-	const user = await getPublicUser(req.user_id, { guilds: true });
-
-	if (user.guilds.length >= maxGuilds) {
+	const guild_count = await Member.count({ where: { id: req.user_id } });
+	if (guild_count >= maxGuilds) {
 		throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403);
 	}
 
 	const guild_id = Snowflake.generate();
-	const guild: Guild = {
+	const guild = new Guild({
 		name: body.name,
 		region: Config.get().regions.default,
 		owner_id: req.user_id,
@@ -62,7 +59,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
 		},
 		widget_channel_id: undefined,
 		widget_enabled: false
-	};
+	});
 
 	const [guild_doc, role] = await Promise.all([
 		new Guild(guild).save(),
@@ -94,14 +91,14 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
 		body.channels?.map((x) => {
 			var id = ids.get(x.id) || Snowflake.generate();
 
-			// TODO: should we abort if parent_id is a category? (or not to allow sub category channels)
+			// TODO: should we abort if parent_id is a category? (to disallow sub category channels)
 			var parent_id = ids.get(x.parent_id);
 
 			return createChannel({ ...x, guild_id, id, parent_id }, req.user_id, { keepId: true, skipExistsCheck: true });
 		})
 	);
 
-	await addMember(req.user_id, guild_id);
+	await Member.addToGuild(req.user_id, guild_id);
 
 	res.status(201).json({ id: guild.id });
 });
diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts
index b8c1012d..7a8ac886 100644
--- a/api/src/routes/guilds/templates/index.ts
+++ b/api/src/routes/guilds/templates/index.ts
@@ -1,10 +1,9 @@
 import { Request, Response, Router } from "express";
 const router: Router = Router();
-import { Template, Guild, Role, Snowflake, Config, User } from "@fosscord/util";
+import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { GuildTemplateCreateSchema } from "../../../schema/Guild";
 import { check } from "../../../util/instanceOf";
-import { addMember } from "../../../util/Member";
 
 router.get("/:code", async (req: Request, res: Response) => {
 	const { code } = req.params;
@@ -19,9 +18,9 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res
 	const body = req.body as GuildTemplateCreateSchema;
 
 	const { maxGuilds } = Config.get().limits.user;
-	const user = await User.getPublicUser(req.user_id, { guilds: true });
 
-	if (user.guilds.length >= maxGuilds) {
+	const guild_count = await Member.count({ id: req.user_id });
+	if (guild_count >= maxGuilds) {
 		throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403);
 	}
 
@@ -29,15 +28,13 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res
 
 	const guild_id = Snowflake.generate();
 
-	const guild: Guild = {
-		...body,
-		...template.serialized_source_guild,
-		id: guild_id,
-		owner_id: req.user_id
-	};
-
-	const [guild_doc, role] = await Promise.all([
-		new Guild(guild).save(),
+	const [guild, role] = await Promise.all([
+		new Guild({
+			...body,
+			...template.serialized_source_guild,
+			id: guild_id,
+			owner_id: req.user_id
+		}).save(),
 		new Role({
 			id: guild_id,
 			guild_id: guild_id,
@@ -52,7 +49,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res
 		}).save()
 	]);
 
-	await addMember(req.user_id, guild_id, { guild: guild_doc });
+	await Member.addToGuild(req.user_id, guild_id);
 
 	res.status(201).json({ id: guild.id });
 });