summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts2
-rw-r--r--api/src/routes/channels/#channel_id/permissions.ts2
-rw-r--r--api/src/routes/channels/#channel_id/typing.ts2
-rw-r--r--api/src/routes/guilds/#guild_id/index.ts4
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/index.ts10
-rw-r--r--api/src/routes/guilds/#guild_id/widget.json.ts2
-rw-r--r--api/src/routes/guilds/index.ts2
-rw-r--r--api/src/routes/guilds/templates/index.ts2
-rw-r--r--api/src/routes/users/#id/profile.ts10
-rw-r--r--api/src/routes/users/@me/delete.ts11
-rw-r--r--api/src/routes/users/@me/guilds.ts4
-rw-r--r--api/src/routes/users/@me/profile.ts27
-rw-r--r--gateway/src/listener/listener.ts2
-rw-r--r--gateway/src/opcodes/Identify.ts2
-rw-r--r--gateway/src/opcodes/LazyRequest.ts1
-rw-r--r--util/src/entities/Member.ts63
-rw-r--r--util/src/util/Permissions.ts2
17 files changed, 68 insertions, 80 deletions
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
index f60484b5..37168940 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -136,7 +136,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => {
 
 	await Message.update({ id: message_id, channel_id }, message);
 
-	const member = channel.guild_id && (await Member.findOneOrFail({ id: req.user_id }));
+	const member = channel.guild_id && (await Member.findOneOrFail({ user_id: req.user_id }));
 
 	await emitEvent({
 		event: "MESSAGE_REACTION_ADD",
diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts
index 9c49542b..97f21659 100644
--- a/api/src/routes/channels/#channel_id/permissions.ts
+++ b/api/src/routes/channels/#channel_id/permissions.ts
@@ -20,7 +20,7 @@ router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number,
 	if (body.type === 0) {
 		if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404);
 	} else if (body.type === 1) {
-		if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404);
+		if (!(await Member.count({ user_id: overwrite_id }))) throw new HTTPError("user not found", 404);
 	} else throw new HTTPError("type not supported", 501);
 
 	// @ts-ignore
diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts
index f1fb3c86..aef99103 100644
--- a/api/src/routes/channels/#channel_id/typing.ts
+++ b/api/src/routes/channels/#channel_id/typing.ts
@@ -10,7 +10,7 @@ router.post("/", async (req: Request, res: Response) => {
 	const user_id = req.user_id;
 	const timestamp = Date.now();
 	const channel = await Channel.findOneOrFail({ id: channel_id });
-	const member = await Member.findOneOrFail({ id: user_id });
+	const member = await Member.findOneOrFail({ user_id: user_id });
 
 	await emitEvent({
 		event: "TYPING_START",
diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts
index 6f55be3b..80b5c609 100644
--- a/api/src/routes/guilds/#guild_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/index.ts
@@ -14,8 +14,8 @@ router.get("/", async (req: Request, res: Response) => {
 
 	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({ id: req.user_id })
+		Member.count({ guild_id: guild_id, user_id: req.user_id }),
+		Member.findOneOrFail({ user_id: req.user_id })
 	]);
 	if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
 
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 d9ce91c0..733a64c4 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
@@ -21,7 +21,7 @@ router.get("/", async (req: Request, res: Response) => {
 	const { guild_id, member_id } = req.params;
 	await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	const member = await Member.findOneOrFail({ id: member_id, guild_id });
+	const member = await Member.findOneOrFail({ user_id: member_id, guild_id });
 
 	return res.json(member);
 });
@@ -29,13 +29,17 @@ router.get("/", async (req: Request, res: Response) => {
 router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) => {
 	const { guild_id, member_id } = req.params;
 	const body = req.body as MemberChangeSchema;
+
+	const permission = await getPermission(req.user_id, guild_id);
+
 	if (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
+
+		permission.hasThrow("MANAGE_ROLES");
 	}
 
-	const member = await Member.findOneOrFail({ id: member_id, guild_id });
+	const member = await Member.findOneOrFail({ user_id: member_id, guild_id });
 	member.assign(req.body);
 
 	Promise.all([
diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts
index ae1f0599..193ed095 100644
--- a/api/src/routes/guilds/#guild_id/widget.json.ts
+++ b/api/src/routes/guilds/#guild_id/widget.json.ts
@@ -63,7 +63,7 @@ router.get("/", async (req: Request, res: Response) => {
 
 	// Fetch members
 	// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
-	let members = await Member.find({ where: { guild_id: guild_id } });
+	let members = await Member.find({ 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 a54b83ba..92525317 100644
--- a/api/src/routes/guilds/index.ts
+++ b/api/src/routes/guilds/index.ts
@@ -13,7 +13,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
 	const body = req.body as GuildCreateSchema;
 
 	const { maxGuilds } = Config.get().limits.user;
-	const guild_count = await Member.count({ id: req.user_id });
+	const guild_count = await Member.count({ user_id: req.user_id });
 	if (guild_count >= maxGuilds) {
 		throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
 	}
diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts
index 3a619278..16b65c65 100644
--- a/api/src/routes/guilds/templates/index.ts
+++ b/api/src/routes/guilds/templates/index.ts
@@ -20,7 +20,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res
 
 	const { maxGuilds } = Config.get().limits.user;
 
-	const guild_count = await Member.count({ id: req.user_id });
+	const guild_count = await Member.count({ user_id: req.user_id });
 	if (guild_count >= maxGuilds) {
 		throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
 	}
diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts
index afccfed5..8be03b47 100644
--- a/api/src/routes/users/#id/profile.ts
+++ b/api/src/routes/users/#id/profile.ts
@@ -1,9 +1,10 @@
 import { Router, Request, Response } from "express";
-import { User } from "../../../../../util/dist";
+import { PublicConnectedAccount, PublicUser, User, UserPublic } from "../../../../../util/dist";
 
 const router: Router = Router();
 
 router.get("/", 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"] });
 
 	res.json({
@@ -24,4 +25,11 @@ 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/delete.ts b/api/src/routes/users/@me/delete.ts
index 6bfe0b93..e3b54607 100644
--- a/api/src/routes/users/@me/delete.ts
+++ b/api/src/routes/users/@me/delete.ts
@@ -1,6 +1,7 @@
 import { Router, Request, Response } from "express";
 import { Guild, Member, User } from "@fosscord/util";
 import bcrypt from "bcrypt";
+import { HTTPError } from "lambert-server";
 const router = Router();
 
 router.post("/", async (req: Request, res: Response) => {
@@ -9,16 +10,16 @@ router.post("/", async (req: Request, res: Response) => {
 
 	if (user.data.hash) {
 		// guest accounts can delete accounts without password
-		correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
+		correctpass = await bcrypt.compare(req.body.password, user.data.hash);
+		if (!correctpass) {
+			throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+		}
 	}
 
 	// TODO: decrement guild member count
 
 	if (correctpass) {
-		await Promise.all([
-			User.delete({ id: req.user_id }), //Yeetus user deletus
-			Member.delete({ id: req.user_id })
-		]);
+		await Promise.all([User.delete({ id: req.user_id }), Member.delete({ user_id: req.user_id })]);
 
 		res.sendStatus(204);
 	} else {
diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts
index fb88281b..1edb0eb1 100644
--- a/api/src/routes/users/@me/guilds.ts
+++ b/api/src/routes/users/@me/guilds.ts
@@ -6,7 +6,7 @@ import { In } from "typeorm";
 const router: Router = Router();
 
 router.get("/", async (req: Request, res: Response) => {
-	const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } });
+	const members = await Member.find({ relations: ["guild"], where: { user_id: req.user_id } });
 
 	res.json(members.map((x) => x.guild));
 });
@@ -20,7 +20,7 @@ router.delete("/:id", async (req: Request, res: Response) => {
 	if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400);
 
 	await Promise.all([
-		Member.delete({ id: req.user_id, guild_id: guild_id }),
+		Member.delete({ user_id: req.user_id, guild_id: guild_id }),
 		emitEvent({
 			event: "GUILD_DELETE",
 			data: {
diff --git a/api/src/routes/users/@me/profile.ts b/api/src/routes/users/@me/profile.ts
deleted file mode 100644
index 5ba03c68..00000000
--- a/api/src/routes/users/@me/profile.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Router, Request, Response } from "express";
-import { User } from "../../../../../util/dist";
-
-const router: Router = Router();
-
-router.get("/", async (req: Request, res: Response) => {
-	const user = await User.getPublicUser(req.user_id, { relations: ["connected_accounts"] });
-
-	res.json({
-		connected_accounts: user.connected_accounts,
-		premium_guild_since: null, // TODO
-		premium_since: null, // TODO
-		user: {
-			username: user.username,
-			discriminator: user.discriminator,
-			id: user.id,
-			public_flags: user.public_flags,
-			avatar: user.avatar,
-			accent_color: user.accent_color,
-			banner: user.banner,
-			bio: user.bio,
-			bot: user.bot
-		}
-	});
-});
-
-export default router;
diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts
index 75ca1680..67837e8d 100644
--- a/gateway/src/listener/listener.ts
+++ b/gateway/src/listener/listener.ts
@@ -26,7 +26,7 @@ import { Recipient } from "../../../util/dist/entities/Recipient";
 
 // TODO: use already queried guilds/channels of Identify and don't fetch them again
 export async function setupListener(this: WebSocket) {
-	const members = await Member.find({ where: { id: this.user_id } });
+	const members = await Member.find({ user_id: this.user_id });
 	const guild_ids = members.map((x) => x.guild_id);
 	const user = await User.findOneOrFail({ id: this.user_id });
 	const recipients = await Recipient.find({ where: { id: this.user_id }, relations: ["channel"] });
diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index 958f1b73..4a1b7a45 100644
--- a/gateway/src/opcodes/Identify.ts
+++ b/gateway/src/opcodes/Identify.ts
@@ -56,7 +56,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 	}
 
 	const members = await Member.find({
-		where: { id: this.user_id },
+		where: { user_id: this.user_id },
 		relations: ["guild", "guild.channels", "guild.emojis", "guild.roles", "guild.stickers", "user", "roles"],
 	});
 	const merged_members = members.map((x: any) => {
diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts
index 9f514f5f..146175b5 100644
--- a/gateway/src/opcodes/LazyRequest.ts
+++ b/gateway/src/opcodes/LazyRequest.ts
@@ -8,6 +8,7 @@ import { check } from "./instanceOf";
 
 // TODO: check permission and only show roles/members that have access to this channel
 // TODO: config: if want to list all members (even those who are offline) sorted by role, or just those who are online
+// TODO: rewrite typeorm
 
 export async function onLazyRequest(this: WebSocket, { d }: Payload) {
 	// TODO: check data
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index e1773730..e0dd411a 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -1,6 +1,6 @@
 import { PublicUser, User } from "./User";
 import { BaseClass } from "./BaseClass";
-import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
+import { Column, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
 import { Guild } from "./Guild";
 import { Config, emitEvent } from "../util";
 import {
@@ -12,15 +12,19 @@ import {
 } from "../interfaces";
 import { HTTPError } from "lambert-server";
 import { Role } from "./Role";
-import { ReadState } from "./ReadState";
 
 @Entity("members")
+@Index(["user_id", "guild_id"], { unique: true })
 export class Member extends BaseClass {
-	@JoinColumn({ name: "id" })
+	@Column()
+	@RelationId((member: Member) => member.user)
+	user_id: string;
+
+	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User)
 	user: User;
 
-	@Column({ nullable: true })
+	@Column()
 	@RelationId((member: Member) => member.guild)
 	guild_id: string;
 
@@ -58,20 +62,20 @@ export class Member extends BaseClass {
 	// read_state: ReadState;
 
 	static async IsInGuildOrFail(user_id: string, guild_id: string) {
-		if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true;
+		if (await Member.count({ user_id: user_id, guild: { id: guild_id } })) return true;
 		throw new HTTPError("You are not member of this guild", 403);
 	}
 
 	static async removeFromGuild(user_id: string, guild_id: string) {
 		const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } });
 		if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild");
-		const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] });
+		const member = await Member.findOneOrFail({ where: { user_id, guild_id }, relations: ["user"] });
 
 		// use promise all to execute all promises at the same time -> save time
 		return Promise.all([
 			Member.delete({
-				id: user_id,
-				guild_id: guild_id,
+				user_id,
+				guild_id,
 			}),
 			Guild.decrement({ id: guild_id }, "member_count", -1),
 
@@ -84,11 +88,8 @@ export class Member extends BaseClass {
 			} as GuildDeleteEvent),
 			emitEvent({
 				event: "GUILD_MEMBER_REMOVE",
-				data: {
-					guild_id: guild_id,
-					user: member.user,
-				},
-				guild_id: guild_id,
+				data: { guild_id, user: member.user },
+				guild_id,
 			} as GuildMemberRemoveEvent),
 		]);
 	}
@@ -97,11 +98,11 @@ export class Member extends BaseClass {
 		const [member] = await Promise.all([
 			// @ts-ignore
 			Member.findOneOrFail({
-				where: { id: user_id, guild_id: guild_id },
+				where: { user_id: user_id, guild_id },
 				relations: ["user", "roles"], // we don't want to load  the role objects just the ids
 				select: ["roles.id"],
 			}),
-			await Role.findOneOrFail({ id: role_id, guild_id: guild_id }),
+			await Role.findOneOrFail({ id: role_id, guild_id }),
 		]);
 		member.roles.push(new Role({ id: role_id }));
 
@@ -110,11 +111,11 @@ export class Member extends BaseClass {
 			emitEvent({
 				event: "GUILD_MEMBER_UPDATE",
 				data: {
-					guild_id: guild_id,
+					guild_id,
 					user: member.user,
 					roles: member.roles.map((x) => x.id),
 				},
-				guild_id: guild_id,
+				guild_id,
 			} as GuildMemberUpdateEvent),
 		]);
 	}
@@ -123,11 +124,11 @@ export class Member extends BaseClass {
 		const [member] = await Promise.all([
 			// @ts-ignore
 			Member.findOneOrFail({
-				where: { id: user_id, guild_id: guild_id },
+				where: { user_id, guild_id },
 				relations: ["user", "roles"], // we don't want to load  the role objects just the ids
 				select: ["roles.id"],
 			}),
-			await Role.findOneOrFail({ id: role_id, guild_id: guild_id }),
+			await Role.findOneOrFail({ id: role_id, guild_id }),
 		]);
 		member.roles = member.roles.filter((x) => x.id == role_id);
 
@@ -136,11 +137,11 @@ export class Member extends BaseClass {
 			emitEvent({
 				event: "GUILD_MEMBER_UPDATE",
 				data: {
-					guild_id: guild_id,
+					guild_id,
 					user: member.user,
 					roles: member.roles.map((x) => x.id),
 				},
-				guild_id: guild_id,
+				guild_id,
 			} as GuildMemberUpdateEvent),
 		]);
 	}
@@ -148,8 +149,8 @@ export class Member extends BaseClass {
 	static async changeNickname(user_id: string, guild_id: string, nickname: string) {
 		const member = await Member.findOneOrFail({
 			where: {
-				id: user_id,
-				guild_id: guild_id,
+				user_id: user_id,
+				guild_id,
 			},
 			relations: ["user"],
 		});
@@ -161,11 +162,11 @@ export class Member extends BaseClass {
 			emitEvent({
 				event: "GUILD_MEMBER_UPDATE",
 				data: {
-					guild_id: guild_id,
+					guild_id,
 					user: member.user,
 					nick: nickname,
 				},
-				guild_id: guild_id,
+				guild_id,
 			} as GuildMemberUpdateEvent),
 		]);
 	}
@@ -174,7 +175,7 @@ export class Member extends BaseClass {
 		const user = await User.getPublicUser(user_id);
 
 		const { maxGuilds } = Config.get().limits.user;
-		const guild_count = await Member.count({ id: user_id });
+		const guild_count = await Member.count({ user_id: user_id });
 		if (guild_count >= maxGuilds) {
 			throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
 		}
@@ -186,12 +187,12 @@ export class Member extends BaseClass {
 			relations: ["channels", "emojis", "members", "roles", "stickers"],
 		});
 
-		if (await Member.count({ id: user.id, guild: { id: guild_id } }))
+		if (await Member.count({ user_id: user.id, guild: { id: guild_id } }))
 			throw new HTTPError("You are already a member of this guild", 400);
 
 		const member = {
-			id: user_id,
-			guild_id: guild_id,
+			user_id,
+			guild_id,
 			nick: undefined,
 			roles: [guild_id], // @everyone role
 			joined_at: new Date(),
@@ -224,9 +225,9 @@ export class Member extends BaseClass {
 				data: {
 					...member,
 					user,
-					guild_id: guild_id,
+					guild_id,
 				},
-				guild_id: guild_id,
+				guild_id,
 			} as GuildMemberAddEvent),
 			emitEvent({
 				event: "GUILD_CREATE",
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index 89b316ee..fcf1546b 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -220,7 +220,7 @@ export async function getPermission(user_id?: string, guild_id?: string, channel
 		guild = await Guild.findOneOrFail({ id: guild_id });
 		if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
 
-		member = await Member.findOneOrFail({ where: { guild: guild_id, id: user_id }, relations: ["roles"] });
+		member = await Member.findOneOrFail({ where: { guild_id, user_id }, relations: ["roles"] });
 	}
 
 	// TODO: remove guild.roles and convert recipient_ids to recipients