summary refs log tree commit diff
path: root/api/src/routes/users
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/routes/users')
-rw-r--r--api/src/routes/users/#id/index.ts5
-rw-r--r--api/src/routes/users/#id/profile.ts20
-rw-r--r--api/src/routes/users/@me/affinities/guilds.ts3
-rw-r--r--api/src/routes/users/@me/affinities/users.ts (renamed from api/src/routes/users/@me/affinities/user.ts)3
-rw-r--r--api/src/routes/users/@me/applications/#app_id/entitlements.ts11
-rw-r--r--api/src/routes/users/@me/billing/country-code.ts11
-rw-r--r--api/src/routes/users/@me/billing/subscriptions.ts11
-rw-r--r--api/src/routes/users/@me/channels.ts50
-rw-r--r--api/src/routes/users/@me/connections.ts11
-rw-r--r--api/src/routes/users/@me/delete.ts4
-rw-r--r--api/src/routes/users/@me/devices.ts3
-rw-r--r--api/src/routes/users/@me/disable.ts3
-rw-r--r--api/src/routes/users/@me/guilds.ts6
-rw-r--r--api/src/routes/users/@me/index.ts42
-rw-r--r--api/src/routes/users/@me/library.ts3
-rw-r--r--api/src/routes/users/@me/relationships.ts235
-rw-r--r--api/src/routes/users/@me/settings.ts7
17 files changed, 253 insertions, 175 deletions
diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts
index 3841756b..bdb1060f 100644
--- a/api/src/routes/users/#id/index.ts
+++ b/api/src/routes/users/#id/index.ts
@@ -1,9 +1,10 @@
 import { Router, Request, Response } from "express";
-import { User } from "../../../../../util/dist";
+import { User } from "@fosscord/util";
+import { route } from "@fosscord/api";
 
 const router: Router = Router();
 
-router.get("/", async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
 	const { id } = req.params;
 
 	res.json(await User.getPublicUser(id));
diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts
index 8be03b47..15457547 100644
--- a/api/src/routes/users/#id/profile.ts
+++ b/api/src/routes/users/#id/profile.ts
@@ -1,9 +1,17 @@
 import { Router, Request, Response } from "express";
-import { PublicConnectedAccount, PublicUser, User, UserPublic } from "../../../../../util/dist";
+import { PublicConnectedAccount, PublicUser, User, UserPublic } from "@fosscord/util";
+import { route } from "@fosscord/api";
 
 const router: Router = Router();
 
-router.get("/", async (req: Request, res: Response) => {
+export interface UserProfileResponse {
+	user: UserPublic;
+	connected_accounts: PublicConnectedAccount;
+	premium_guild_since?: Date;
+	premium_since?: Date;
+}
+
+router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
 	if (req.params.id === "@me") req.params.id = req.user_id;
 	const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
 
@@ -11,6 +19,7 @@ router.get("/", async (req: Request, res: Response) => {
 		connected_accounts: user.connected_accounts,
 		premium_guild_since: null, // TODO
 		premium_since: null, // TODO
+		mutual_guilds: [], // TODO {id: "", nick: null} when ?with_mutual_guilds=true
 		user: {
 			username: user.username,
 			discriminator: user.discriminator,
@@ -25,11 +34,4 @@ router.get("/", async (req: Request, res: Response) => {
 	});
 });
 
-export interface UserProfileResponse {
-	user: UserPublic;
-	connected_accounts: PublicConnectedAccount;
-	premium_guild_since?: Date;
-	premium_since?: Date;
-}
-
 export default router;
diff --git a/api/src/routes/users/@me/affinities/guilds.ts b/api/src/routes/users/@me/affinities/guilds.ts
index fa6be0e7..8d744744 100644
--- a/api/src/routes/users/@me/affinities/guilds.ts
+++ b/api/src/routes/users/@me/affinities/guilds.ts
@@ -1,8 +1,9 @@
 import { Router, Response, Request } from "express";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
-router.get("/", (req: Request, res: Response) => {
+router.get("/", route({}), (req: Request, res: Response) => {
 	// TODO:
 	res.status(200).send({ guild_affinities: [] });
 });
diff --git a/api/src/routes/users/@me/affinities/user.ts b/api/src/routes/users/@me/affinities/users.ts
index 0790a8a4..6d4e4991 100644
--- a/api/src/routes/users/@me/affinities/user.ts
+++ b/api/src/routes/users/@me/affinities/users.ts
@@ -1,8 +1,9 @@
 import { Router, Response, Request } from "express";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
-router.get("/", (req: Request, res: Response) => {
+router.get("/", route({}), (req: Request, res: Response) => {
 	// TODO:
 	res.status(200).send({ user_affinities: [], inverse_user_affinities: [] });
 });
diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/api/src/routes/users/@me/applications/#app_id/entitlements.ts
new file mode 100644
index 00000000..411e95bf
--- /dev/null
+++ b/api/src/routes/users/@me/applications/#app_id/entitlements.ts
@@ -0,0 +1,11 @@
+import { Request, Response, Router } from "express";
+import { route } from "@fosscord/api";
+
+const router: Router = Router();
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	//TODO
+	res.json([]).status(200);
+});
+
+export default router;
diff --git a/api/src/routes/users/@me/billing/country-code.ts b/api/src/routes/users/@me/billing/country-code.ts
new file mode 100644
index 00000000..33d40796
--- /dev/null
+++ b/api/src/routes/users/@me/billing/country-code.ts
@@ -0,0 +1,11 @@
+import { Request, Response, Router } from "express";
+import { route } from "@fosscord/api";
+
+const router: Router = Router();
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	//TODO
+	res.json({ country_code: "US" }).status(200);
+});
+
+export default router;
diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/api/src/routes/users/@me/billing/subscriptions.ts
new file mode 100644
index 00000000..411e95bf
--- /dev/null
+++ b/api/src/routes/users/@me/billing/subscriptions.ts
@@ -0,0 +1,11 @@
+import { Request, Response, Router } from "express";
+import { route } from "@fosscord/api";
+
+const router: Router = Router();
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	//TODO
+	res.json([]).status(200);
+});
+
+export default router;
diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts
index 6fd396b8..b5782eca 100644
--- a/api/src/routes/users/@me/channels.ts
+++ b/api/src/routes/users/@me/channels.ts
@@ -1,46 +1,22 @@
-import { Router, Request, Response } from "express";
-import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
-
-import { DmChannelCreateSchema } from "../../../schema/Channel";
-import { check } from "../../../util/instanceOf";
-import { In } from "typeorm";
-import { Recipient } from "../../../../../util/dist/entities/Recipient";
+import { Request, Response, Router } from "express";
+import { Recipient, DmChannelDTO, Channel } from "@fosscord/util";
+import { route } from "@fosscord/api";
 
 const router: Router = Router();
 
-router.get("/", async (req: Request, res: Response) => {
-	const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] });
-
-	res.json(recipients.map((x) => x.channel));
+router.get("/", route({}), async (req: Request, res: Response) => {
+	const recipients = await Recipient.find({ where: { user_id: req.user_id, closed: false }, relations: ["channel", "channel.recipients"] });
+	res.json(await Promise.all(recipients.map(r => DmChannelDTO.from(r.channel, [req.user_id]))));
 });
 
-router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Response) => {
-	const body = req.body as DmChannelCreateSchema;
-
-	body.recipients = body.recipients.filter((x) => x !== req.user_id).unique();
-
-	const recipients = await User.find({ id: In(body.recipients) });
+export interface DmChannelCreateSchema {
+	name?: string;
+	recipients: string[];
+}
 
-	if (recipients.length !== body.recipients.length) {
-		throw new HTTPError("Recipient/s not found");
-	}
-
-	const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
-	const name = trimSpecial(body.name);
-
-	const channel = await new Channel({
-		name,
-		type,
-		owner_id: req.user_id,
-		created_at: new Date(),
-		last_message_id: null,
-		recipients: [...body.recipients.map((x) => new Recipient({ id: x })), new Recipient({ id: req.user_id })]
-	}).save();
-
-	await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent);
-
-	res.json(channel);
+router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
+	const body = req.body as DmChannelCreateSchema;
+	res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
 });
 
 export default router;
diff --git a/api/src/routes/users/@me/connections.ts b/api/src/routes/users/@me/connections.ts
new file mode 100644
index 00000000..411e95bf
--- /dev/null
+++ b/api/src/routes/users/@me/connections.ts
@@ -0,0 +1,11 @@
+import { Request, Response, Router } from "express";
+import { route } from "@fosscord/api";
+
+const router: Router = Router();
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	//TODO
+	res.json([]).status(200);
+});
+
+export default router;
diff --git a/api/src/routes/users/@me/delete.ts b/api/src/routes/users/@me/delete.ts
index e5fda948..39ceefd9 100644
--- a/api/src/routes/users/@me/delete.ts
+++ b/api/src/routes/users/@me/delete.ts
@@ -1,10 +1,12 @@
 import { Router, Request, Response } from "express";
 import { Guild, Member, User } from "@fosscord/util";
+import { route } from "@fosscord/api";
 import bcrypt from "bcrypt";
 import { HTTPError } from "lambert-server";
+
 const router = Router();
 
-router.post("/", async (req: Request, res: Response) => {
+router.post("/", route({}), async (req: Request, res: Response) => {
 	const user = await User.findOneOrFail({ id: req.user_id }); //User object
 	let correctpass = true;
 
diff --git a/api/src/routes/users/@me/devices.ts b/api/src/routes/users/@me/devices.ts
index b16ef783..8556a3ad 100644
--- a/api/src/routes/users/@me/devices.ts
+++ b/api/src/routes/users/@me/devices.ts
@@ -1,8 +1,9 @@
 import { Router, Response, Request } from "express";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
-router.post("/", (req: Request, res: Response) => {
+router.post("/", route({}), (req: Request, res: Response) => {
 	// TODO:
 	res.sendStatus(204);
 });
diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts
index 7b8a130c..259ced96 100644
--- a/api/src/routes/users/@me/disable.ts
+++ b/api/src/routes/users/@me/disable.ts
@@ -1,10 +1,11 @@
 import { User } from "@fosscord/util";
 import { Router, Response, Request } from "express";
+import { route } from "@fosscord/api";
 import bcrypt from "bcrypt";
 
 const router = Router();
 
-router.post("/", async (req: Request, res: Response) => {
+router.post("/", route({}), async (req: Request, res: Response) => {
 	const user = await User.findOneOrFail({ id: req.user_id }); //User object
 	let correctpass = true;
 
diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts
index fb88281b..4ba03cec 100644
--- a/api/src/routes/users/@me/guilds.ts
+++ b/api/src/routes/users/@me/guilds.ts
@@ -1,18 +1,18 @@
 import { Router, Request, Response } from "express";
 import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
-import { In } from "typeorm";
+import { route } from "@fosscord/api";
 
 const router: Router = Router();
 
-router.get("/", async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
 	const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } });
 
 	res.json(members.map((x) => x.guild));
 });
 
 // user send to leave a certain guild
-router.delete("/:id", async (req: Request, res: Response) => {
+router.delete("/:id", route({}), async (req: Request, res: Response) => {
 	const guild_id = req.params.id;
 	const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] });
 
diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts
index 68649215..f6bb04d7 100644
--- a/api/src/routes/users/@me/index.ts
+++ b/api/src/routes/users/@me/index.ts
@@ -1,23 +1,47 @@
 import { Router, Request, Response } from "express";
-import { User, PrivateUserProjection } from "@fosscord/util";
-import { UserModifySchema } from "../../../schema/User";
-import { check } from "../../../util/instanceOf";
-import { handleFile } from "../../../util/cdn";
+import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile } from "@fosscord/util";
+import { route } from "@fosscord/api";
 
 const router: Router = Router();
 
-router.get("/", async (req: Request, res: Response) => {
-	res.json(await User.getPublicUser(req.user_id, { select: PrivateUserProjection }));
+export interface UserModifySchema {
+	/**
+	 * @minLength 1
+	 * @maxLength 100
+	 */
+	username?: string;
+	avatar?: string | null;
+	/**
+	 * @maxLength 1024
+	 */
+	bio?: string;
+	accent_color?: number;
+	banner?: string | null;
+	password?: string;
+	new_password?: string;
+	code?: string;
+}
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } }));
 });
 
-router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => {
+router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => {
 	const body = req.body as UserModifySchema;
 
 	if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
 	if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
 
-	const user = await new User({ ...body, id: req.user_id }).save();
-	// TODO: dispatch user update event
+	await new User({ ...body, id: req.user_id }).save();
+
+	//Need to reload user from db due to https://github.com/typeorm/typeorm/issues/3490
+	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: PrivateUserProjection });
+	// TODO: send update member list event in gateway
+	await emitEvent({
+		event: "USER_UPDATE",
+		user_id: req.user_id,
+		data: user
+	} as UserUpdateEvent);
 
 	res.json(user);
 });
diff --git a/api/src/routes/users/@me/library.ts b/api/src/routes/users/@me/library.ts
index d771cb5e..7ac13bae 100644
--- a/api/src/routes/users/@me/library.ts
+++ b/api/src/routes/users/@me/library.ts
@@ -1,8 +1,9 @@
 import { Router, Response, Request } from "express";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
-router.get("/", (req: Request, res: Response) => {
+router.get("/", route({}), (req: Request, res: Response) => {
 	// TODO:
 	res.status(200).send([]);
 });
diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts
index 8d6d8c9e..567c734e 100644
--- a/api/src/routes/users/@me/relationships.ts
+++ b/api/src/routes/users/@me/relationships.ts
@@ -11,61 +11,149 @@ import {
 import { Router, Response, Request } from "express";
 import { HTTPError } from "lambert-server";
 import { DiscordApiErrors } from "@fosscord/util";
-
-import { check, Length } from "../../../util/instanceOf";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
 const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
 
-router.get("/", async (req: Request, res: Response) => {
-	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["relationships"] });
+router.get("/", route({}), async (req: Request, res: Response) => {
+	const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] });
+
+	//TODO DTO
+	const related_users = user.relationships.map((r) => {
+		return {
+			id: r.to.id,
+			type: r.type,
+			nickname: null,
+			user: r.to.toPublicUser()
+		};
+	});
 
-	return res.json(user.relationships);
+	return res.json(related_users);
 });
 
+export interface RelationshipPutSchema {
+	type?: RelationshipType;
+}
+
+router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => {
+	return await updateRelationship(
+		req,
+		res,
+		await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }),
+		req.body.type ?? RelationshipType.friends
+	);
+});
+
+export interface RelationshipPostSchema {
+	discriminator: string;
+	username: string;
+}
+
+router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => {
+	return await updateRelationship(
+		req,
+		res,
+		await User.findOneOrFail({
+			relations: ["relationships", "relationships.to"],
+			select: userProjection,
+			where: {
+				discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes
+				username: req.body.username
+			}
+		}),
+		req.body.type
+	);
+});
+
+router.delete("/:id", route({}), async (req: Request, res: Response) => {
+	const { id } = req.params;
+	if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
+
+	const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] });
+	const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] });
+
+	const relationship = user.relationships.find((x) => x.to_id === id);
+	const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+
+	if (!relationship) throw new HTTPError("You are not friends with the user", 404);
+	if (relationship?.type === RelationshipType.blocked) {
+		// unblock user
+
+		await Promise.all([
+			Relationship.delete({ id: relationship.id }),
+			emitEvent({
+				event: "RELATIONSHIP_REMOVE",
+				user_id: req.user_id,
+				data: relationship.toPublicRelationship()
+			} as RelationshipRemoveEvent)
+		]);
+		return res.sendStatus(204);
+	}
+	if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
+		await Promise.all([
+			Relationship.delete({ id: friendRequest.id }),
+			await emitEvent({
+				event: "RELATIONSHIP_REMOVE",
+				data: friendRequest.toPublicRelationship(),
+				user_id: id
+			} as RelationshipRemoveEvent)
+		]);
+	}
+
+	await Promise.all([
+		Relationship.delete({ id: relationship.id }),
+		emitEvent({
+			event: "RELATIONSHIP_REMOVE",
+			data: relationship.toPublicRelationship(),
+			user_id: req.user_id
+		} as RelationshipRemoveEvent)
+	]);
+
+	return res.sendStatus(204);
+});
+
+export default router;
+
 async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) {
 	const id = friend.id;
 	if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
 
-	const user = await User.findOneOrFail({ id: req.user_id }, { relations: ["relationships"], select: userProjection });
+	const user = await User.findOneOrFail(
+		{ id: req.user_id },
+		{ relations: ["relationships", "relationships.to"], select: userProjection }
+	);
 
-	var relationship = user.relationships.find((x) => x.id === id);
-	const friendRequest = friend.relationships.find((x) => x.id === req.user_id);
+	var relationship = user.relationships.find((x) => x.to_id === id);
+	const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
 
 	// TODO: you can add infinitely many blocked users (should this be prevented?)
 	if (type === RelationshipType.blocked) {
 		if (relationship) {
 			if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user");
 			relationship.type = RelationshipType.blocked;
+			await relationship.save();
 		} else {
-			relationship = new Relationship({ id, type: RelationshipType.blocked });
-			user.relationships.push(relationship);
+			relationship = await new Relationship({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save();
 		}
 
 		if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
-			friend.relationships.remove(friendRequest);
 			await Promise.all([
-				user.save(),
+				Relationship.delete({ id: friendRequest.id }),
 				emitEvent({
 					event: "RELATIONSHIP_REMOVE",
-					data: friendRequest,
+					data: friendRequest.toPublicRelationship(),
 					user_id: id
 				} as RelationshipRemoveEvent)
 			]);
 		}
 
-		await Promise.all([
-			user.save(),
-			emitEvent({
-				event: "RELATIONSHIP_ADD",
-				data: {
-					...relationship,
-					user: { ...friend }
-				},
-				user_id: req.user_id
-			} as RelationshipAddEvent)
-		]);
+		await emitEvent({
+			event: "RELATIONSHIP_ADD",
+			data: relationship.toPublicRelationship(),
+			user_id: req.user_id
+		} as RelationshipAddEvent);
 
 		return res.sendStatus(204);
 	}
@@ -73,40 +161,43 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
 	const { maxFriends } = Config.get().limits.user;
 	if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
 
-	var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, id: req.user_id });
-	var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing, id });
+	var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
+	var outgoing_relationship = new Relationship({
+		nickname: undefined,
+		type: RelationshipType.outgoing,
+		to: friend,
+		from: user
+	});
 
 	if (friendRequest) {
 		if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
+		if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
 		// accept friend request
 		incoming_relationship = friendRequest;
 		incoming_relationship.type = RelationshipType.friends;
-		outgoing_relationship.type = RelationshipType.friends;
-	} else friend.relationships.push(incoming_relationship);
+	}
 
 	if (relationship) {
 		if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
 		if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
 		if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
-	} else user.relationships.push(outgoing_relationship);
+		outgoing_relationship = relationship;
+		outgoing_relationship.type = RelationshipType.friends;
+	}
 
 	await Promise.all([
-		user.save(),
-		friend.save(),
+		incoming_relationship.save(),
+		outgoing_relationship.save(),
 		emitEvent({
 			event: "RELATIONSHIP_ADD",
-			data: {
-				...outgoing_relationship,
-				user: { ...friend }
-			},
+			data: outgoing_relationship.toPublicRelationship(),
 			user_id: req.user_id
 		} as RelationshipAddEvent),
 		emitEvent({
 			event: "RELATIONSHIP_ADD",
 			data: {
-				...incoming_relationship,
-				should_notify: true,
-				user: { ...user }
+				...incoming_relationship.toPublicRelationship(),
+				should_notify: true
 			},
 			user_id: id
 		} as RelationshipAddEvent)
@@ -114,71 +205,3 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
 
 	return res.sendStatus(204);
 }
-
-router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Request, res: Response) => {
-	return await updateRelationship(
-		req,
-		res,
-		await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }),
-		req.body.type
-	);
-});
-
-router.post("/", check({ discriminator: String, username: String }), async (req: Request, res: Response) => {
-	return await updateRelationship(
-		req,
-		res,
-		await User.findOneOrFail({
-			relations: ["relationships"],
-			select: userProjection,
-			where: req.body as { discriminator: string; username: string }
-		}),
-		req.body.type
-	);
-});
-
-router.delete("/:id", async (req: Request, res: Response) => {
-	const { id } = req.params;
-	if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
-
-	const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] });
-	const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] });
-
-	const relationship = user.relationships.find((x) => x.id === id);
-	const friendRequest = friend.relationships.find((x) => x.id === req.user_id);
-
-	if (relationship?.type === RelationshipType.blocked) {
-		// unblock user
-		user.relationships.remove(relationship);
-
-		await Promise.all([
-			user.save(),
-			emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent)
-		]);
-		return res.sendStatus(204);
-	}
-	if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404);
-	if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
-
-	user.relationships.remove(relationship);
-	friend.relationships.remove(friendRequest);
-
-	await Promise.all([
-		user.save(),
-		friend.save(),
-		emitEvent({
-			event: "RELATIONSHIP_REMOVE",
-			data: relationship,
-			user_id: req.user_id
-		} as RelationshipRemoveEvent),
-		emitEvent({
-			event: "RELATIONSHIP_REMOVE",
-			data: friendRequest,
-			user_id: id
-		} as RelationshipRemoveEvent)
-	]);
-
-	return res.sendStatus(204);
-});
-
-export default router;
diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts
index 90ee6372..9d7a2545 100644
--- a/api/src/routes/users/@me/settings.ts
+++ b/api/src/routes/users/@me/settings.ts
@@ -1,11 +1,12 @@
 import { Router, Response, Request } from "express";
 import { User, UserSettings } from "@fosscord/util";
-import { check } from "../../../util/instanceOf";
-import { UserSettingsSchema } from "../../../schema/User";
+import { route } from "@fosscord/api";
 
 const router = Router();
 
-router.patch("/", check(UserSettingsSchema), async (req: Request, res: Response) => {
+export interface UserSettingsSchema extends UserSettings {}
+
+router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => {
 	const body = req.body as UserSettings;
 
 	// only users can update user settings