summary refs log tree commit diff
path: root/util
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-08-31 17:54:09 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-08-31 17:54:09 +0200
commit3d929755fbbfea4aa5119ce2a1bb087269fb69e8 (patch)
tree0914d47ed52e9a7b156efb01efcd8e3cdc866d24 /util
parentadded first unittests for api endpoints (diff)
downloadserver-3d929755fbbfea4aa5119ce2a1bb087269fb69e8.tar.xz
:sparkles: delete _ids from entities
Diffstat (limited to 'util')
-rw-r--r--util/package-lock.json14
-rw-r--r--util/package.json2
-rw-r--r--util/src/entities/Application.ts16
-rw-r--r--util/src/entities/Attachment.ts34
-rw-r--r--util/src/entities/AuditLog.ts8
-rw-r--r--util/src/entities/Ban.ts9
-rw-r--r--util/src/entities/BaseClass.ts34
-rw-r--r--util/src/entities/Channel.ts98
-rw-r--r--util/src/entities/Config.ts8
-rw-r--r--util/src/entities/ConnectedAccount.ts3
-rw-r--r--util/src/entities/Emoji.ts11
-rw-r--r--util/src/entities/Guild.ts68
-rw-r--r--util/src/entities/Invite.ts12
-rw-r--r--util/src/entities/Member.ts86
-rw-r--r--util/src/entities/Message.ts78
-rw-r--r--util/src/entities/ReadState.ts9
-rw-r--r--util/src/entities/Recipient.ts19
-rw-r--r--util/src/entities/Relationship.ts3
-rw-r--r--util/src/entities/Role.ts3
-rw-r--r--util/src/entities/Sticker.ts2
-rw-r--r--util/src/entities/Team.ts10
-rw-r--r--util/src/entities/TeamMember.ts6
-rw-r--r--util/src/entities/Template.ts6
-rw-r--r--util/src/entities/User.ts22
-rw-r--r--util/src/entities/VoiceState.ts11
-rw-r--r--util/src/entities/Webhook.ts15
-rw-r--r--util/src/entities/index.ts2
-rw-r--r--util/src/interfaces/Event.ts13
-rw-r--r--util/src/tes.ts12
-rw-r--r--util/src/util/Config.ts2
-rw-r--r--util/src/util/Permissions.ts7
31 files changed, 382 insertions, 241 deletions
diff --git a/util/package-lock.json b/util/package-lock.json
index bb04d0bc..47aca2d1 100644
--- a/util/package-lock.json
+++ b/util/package-lock.json
@@ -24,7 +24,7 @@
 				"reflect-metadata": "^0.1.13",
 				"sqlite3": "^5.0.2",
 				"typeorm": "^0.2.37",
-				"typescript": "^4.3.5",
+				"typescript": "^4.4.2",
 				"typescript-json-schema": "^0.50.1"
 			},
 			"devDependencies": {
@@ -8235,9 +8235,9 @@
 			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
 		},
 		"node_modules/typescript": {
-			"version": "4.3.5",
-			"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
-			"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
+			"version": "4.4.2",
+			"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
+			"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
 			"bin": {
 				"tsc": "bin/tsc",
 				"tsserver": "bin/tsserver"
@@ -15150,9 +15150,9 @@
 			}
 		},
 		"typescript": {
-			"version": "4.3.5",
-			"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
-			"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA=="
+			"version": "4.4.2",
+			"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
+			"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ=="
 		},
 		"typescript-json-schema": {
 			"version": "0.50.1",
diff --git a/util/package.json b/util/package.json
index af14a521..39af7526 100644
--- a/util/package.json
+++ b/util/package.json
@@ -51,7 +51,7 @@
 		"reflect-metadata": "^0.1.13",
 		"sqlite3": "^5.0.2",
 		"typeorm": "^0.2.37",
-		"typescript": "^4.3.5",
+		"typescript": "^4.4.2",
 		"typescript-json-schema": "^0.50.1"
 	},
 	"jest": {
diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts
index b179d171..2092cd4e 100644
--- a/util/src/entities/Application.ts
+++ b/util/src/entities/Application.ts
@@ -2,6 +2,7 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Team } from "./Team";
+import { User } from "./User";
 
 @Entity("applications")
 export class Application extends BaseClass {
@@ -29,8 +30,9 @@ export class Application extends BaseClass {
 	@Column({ nullable: true })
 	privacy_policy_url?: string;
 
-	@Column()
-	owner_id: string;
+	@JoinColumn({ name: "owner_id" })
+	@ManyToOne(() => User)
+	owner?: User;
 
 	@Column({ nullable: true })
 	summary?: string;
@@ -38,18 +40,12 @@ export class Application extends BaseClass {
 	@Column()
 	verify_key: string;
 
-	@RelationId((application: Application) => application.team)
-	team_id: string;
-
 	@JoinColumn({ name: "team_id" })
-	@ManyToOne(() => Team, (team: Team) => team.id)
+	@ManyToOne(() => Team)
 	team?: Team;
 
-	@RelationId((application: Application) => application.guild)
-	guild_id: string;
-
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild: Guild; // if this application is a game sold, this field will be the guild to which it has been linked
 
 	@Column({ nullable: true })
diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts
new file mode 100644
index 00000000..ca893400
--- /dev/null
+++ b/util/src/entities/Attachment.ts
@@ -0,0 +1,34 @@
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+
+@Entity("attachments")
+export class Attachment extends BaseClass {
+	@Column()
+	filename: string; // name of file attached
+
+	@Column()
+	size: number; // size of file in bytes
+
+	@Column()
+	url: string; // source url of file
+
+	@Column()
+	proxy_url: string; // a proxied url of file
+
+	@Column({ nullable: true })
+	height?: number; // height of file (if image)
+
+	@Column({ nullable: true })
+	width?: number; // width of file (if image)
+
+	@Column({ nullable: true })
+	content_type?: string;
+
+	@Column({ nullable: true })
+	@RelationId((attachment: Attachment) => attachment.message)
+	message_id: string;
+
+	@JoinColumn({ name: "message_id" })
+	@ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments)
+	message: import("./Message").Message;
+}
diff --git a/util/src/entities/AuditLog.ts b/util/src/entities/AuditLog.ts
index ea8aa641..ceeb21fd 100644
--- a/util/src/entities/AuditLog.ts
+++ b/util/src/entities/AuditLog.ts
@@ -43,18 +43,16 @@ export enum AuditLogEvents {
 
 @Entity("audit_logs")
 export class AuditLogEntry extends BaseClass {
-	@RelationId((auditlog: AuditLogEntry) => auditlog.target)
-	target_id: string;
-
 	@JoinColumn({ name: "target_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	target?: User;
 
+	@Column({ nullable: true })
 	@RelationId((auditlog: AuditLogEntry) => auditlog.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	user: User;
 
 	@Column({
diff --git a/util/src/entities/Ban.ts b/util/src/entities/Ban.ts
index f1cbd849..e8a6d648 100644
--- a/util/src/entities/Ban.ts
+++ b/util/src/entities/Ban.ts
@@ -5,25 +5,28 @@ import { User } from "./User";
 
 @Entity("bans")
 export class Ban extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((ban: Ban) => ban.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	user: User;
 
+	@Column({ nullable: true })
 	@RelationId((ban: Ban) => ban.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
+	@Column({ nullable: true })
 	@RelationId((ban: Ban) => ban.executor)
 	executor_id: string;
 
 	@JoinColumn({ name: "executor_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	executor: User;
 
 	@Column()
diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts
index 63ce5836..0856ccd1 100644
--- a/util/src/entities/BaseClass.ts
+++ b/util/src/entities/BaseClass.ts
@@ -1,13 +1,5 @@
 import "reflect-metadata";
-import {
-	BaseEntity,
-	BeforeInsert,
-	BeforeUpdate,
-	EntityMetadata,
-	FindConditions,
-	FindManyOptions,
-	PrimaryColumn,
-} from "typeorm";
+import { BaseEntity, BeforeInsert, BeforeUpdate, EntityMetadata, FindConditions, PrimaryColumn } from "typeorm";
 import { Snowflake } from "../util/Snowflake";
 import "missing-native-js-functions";
 
@@ -16,13 +8,12 @@ import "missing-native-js-functions";
 
 export class BaseClass extends BaseEntity {
 	@PrimaryColumn()
-	id: string;
+	id: string = Snowflake.generate();
 
 	// @ts-ignore
-	constructor(public props?: any, public opts: { id?: string } = {}) {
+	constructor(public props?: any) {
 		super();
-
-		this.id = this.opts.id || Snowflake.generate();
+		this.assign(props);
 	}
 
 	get construct(): any {
@@ -35,13 +26,15 @@ export class BaseClass extends BaseEntity {
 
 	assign(props: any) {
 		if (!props || typeof props !== "object") return;
-
-		delete props.id;
 		delete props.opts;
 		delete props.props;
 
-		const properties = new Set(this.metadata.columns.map((x: any) => x.propertyName));
-		// will not include relational properties (e.g. @RelationId @ManyToMany)
+		const properties = new Set(
+			this.metadata.columns
+				.map((x: any) => x.propertyName)
+				.concat(this.metadata.relations.map((x) => x.propertyName))
+		);
+		// will not include relational properties
 
 		for (const key in props) {
 			if (!properties.has(key)) continue;
@@ -65,8 +58,11 @@ export class BaseClass extends BaseEntity {
 	}
 
 	toJSON(): any {
-		// @ts-ignore
-		return Object.fromEntries(this.metadata.columns.map((x) => [x.propertyName, this[x.propertyName]]));
+		return Object.fromEntries(
+			this.metadata.columns // @ts-ignore
+				.map((x) => [x.propertyName, this[x.propertyName]]) // @ts-ignore
+				.concat(this.metadata.relations.map((x) => [x.propertyName, this[x.propertyName]]))
+		);
 	}
 
 	static increment<T extends BaseClass>(conditions: FindConditions<T>, propertyPath: string, value: number | string) {
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 4900f485..e3586dfc 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,8 +1,12 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Message } from "./Message";
 import { User } from "./User";
+import { HTTPError } from "lambert-server";
+import { emitEvent, getPermission, Snowflake } from "../util";
+import { ChannelCreateEvent } from "../interfaces";
+import { Recipient } from "./Recipient";
 
 export enum ChannelType {
 	GUILD_TEXT = 0, // a text channel within a server
@@ -25,40 +29,39 @@ export class Channel extends BaseClass {
 	@Column({ type: "simple-enum", enum: ChannelType })
 	type: ChannelType;
 
-	@Column("simple-array")
-	@RelationId((channel: Channel) => channel.recipients)
-	recipient_ids: string[];
-
-	@JoinColumn({ name: "recipient_ids" })
-	@ManyToMany(() => User, (user: User) => user.id)
-	recipients?: User[];
+	@OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { cascade: true })
+	recipients?: Recipient[];
 
+	@Column({ nullable: true })
 	@RelationId((channel: Channel) => channel.last_message)
 	last_message_id: string;
 
 	@JoinColumn({ name: "last_message_id" })
-	@ManyToOne(() => Message, (message: Message) => message.id)
+	@ManyToOne(() => Message)
 	last_message?: Message;
 
+	@Column({ nullable: true })
 	@RelationId((channel: Channel) => channel.guild)
 	guild_id?: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
+	@Column({ nullable: true })
 	@RelationId((channel: Channel) => channel.parent)
 	parent_id: string;
 
 	@JoinColumn({ name: "parent_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	parent?: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((channel: Channel) => channel.owner)
 	owner_id: string;
 
 	@JoinColumn({ name: "owner_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	owner: User;
 
 	@Column({ nullable: true })
@@ -82,14 +85,77 @@ export class Channel extends BaseClass {
 	@Column({ nullable: true })
 	user_limit?: number;
 
-	@Column()
-	nsfw: boolean;
+	@Column({ nullable: true })
+	nsfw?: boolean;
 
-	@Column()
-	rate_limit_per_user: number;
+	@Column({ nullable: true })
+	rate_limit_per_user?: number;
 
 	@Column({ nullable: true })
 	topic?: string;
+
+	// TODO: DM channel
+	static async createChannel(
+		channel: Partial<Channel>,
+		user_id: string = "0",
+		opts?: {
+			keepId?: boolean;
+			skipExistsCheck?: boolean;
+			skipPermissionCheck?: boolean;
+			skipEventEmit?: boolean;
+		}
+	) {
+		if (!opts?.skipPermissionCheck) {
+			// Always check if user has permission first
+			const permissions = await getPermission(user_id, channel.guild_id);
+			permissions.hasThrow("MANAGE_CHANNELS");
+		}
+
+		switch (channel.type) {
+			case ChannelType.GUILD_TEXT:
+			case ChannelType.GUILD_VOICE:
+				if (channel.parent_id && !opts?.skipExistsCheck) {
+					const exists = await Channel.findOneOrFail({ id: channel.parent_id });
+					if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400);
+					if (exists.guild_id !== channel.guild_id)
+						throw new HTTPError("The category channel needs to be in the guild");
+				}
+				break;
+			case ChannelType.GUILD_CATEGORY:
+				break;
+			case ChannelType.DM:
+			case ChannelType.GROUP_DM:
+				throw new HTTPError("You can't create a dm channel in a guild");
+			// TODO: check if guild is community server
+			case ChannelType.GUILD_STORE:
+			case ChannelType.GUILD_NEWS:
+			default:
+				throw new HTTPError("Not yet supported");
+		}
+
+		if (!channel.permission_overwrites) channel.permission_overwrites = [];
+		// TODO: auto generate position
+
+		channel = {
+			...channel,
+			...(!opts?.keepId && { id: Snowflake.generate() }),
+			created_at: new Date(),
+			position: channel.position || 0,
+		};
+
+		await Promise.all([
+			Channel.insert(channel),
+			!opts?.skipEventEmit
+				? emitEvent({
+						event: "CHANNEL_CREATE",
+						data: channel,
+						guild_id: channel.guild_id,
+				  } as ChannelCreateEvent)
+				: Promise.resolve(),
+		]);
+
+		return channel;
+	}
 }
 
 export interface ChannelPermissionOverwrite {
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index 320a729c..04dc3c36 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -199,12 +199,12 @@ export const DefaultConfigOptions: ConfigValue = {
 					window: 5,
 				},
 				webhook: {
-					count: 5,
-					window: 20,
+					count: 10,
+					window: 5,
 				},
 				channel: {
-					count: 5,
-					window: 20,
+					count: 10,
+					window: 5,
 				},
 				auth: {
 					login: {
diff --git a/util/src/entities/ConnectedAccount.ts b/util/src/entities/ConnectedAccount.ts
index e48bc322..75982d01 100644
--- a/util/src/entities/ConnectedAccount.ts
+++ b/util/src/entities/ConnectedAccount.ts
@@ -4,11 +4,12 @@ import { User } from "./User";
 
 @Entity("connected_accounts")
 export class ConnectedAccount extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((account: ConnectedAccount) => account.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.connected_accounts)
+	@ManyToOne(() => User)
 	user: User;
 
 	@Column({ select: false })
diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts
index 4c0fccd3..181aff2c 100644
--- a/util/src/entities/Emoji.ts
+++ b/util/src/entities/Emoji.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Role } from "./Role";
@@ -15,7 +15,7 @@ export class Emoji extends BaseClass {
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.emojis)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
 	@Column()
@@ -26,11 +26,4 @@ export class Emoji extends BaseClass {
 
 	@Column()
 	require_colons: boolean;
-
-	@RelationId((emoji: Emoji) => emoji.roles)
-	role_ids: string[];
-
-	@JoinColumn({ name: "role_ids" })
-	@ManyToMany(() => Role, (role: Role) => role.id)
-	roles: Role[]; // roles this emoji is whitelisted to (new discord feature?)
 }
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 3e7e8917..c1ef00ac 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -1,20 +1,30 @@
 import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
+import { Ban } from "./Ban";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Emoji } from "./Emoji";
 import { Invite } from "./Invite";
 import { Member } from "./Member";
 import { Role } from "./Role";
+import { Sticker } from "./Sticker";
+import { Template } from "./Template";
 import { User } from "./User";
 import { VoiceState } from "./VoiceState";
+import { Webhook } from "./Webhook";
+
+// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
+// TODO: guild_scheduled_events
+// TODO: stage_instances
+// TODO: threads
 
 @Entity("guilds")
 export class Guild extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.afk_channel)
 	afk_channel_id?: string;
 
 	@JoinColumn({ name: "afk_channel_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	afk_channel?: Channel;
 
 	@Column({ nullable: true })
@@ -25,6 +35,10 @@ export class Guild extends BaseClass {
 	// @Column({ nullable: true })
 	// application?: string;
 
+	@JoinColumn({ name: "ban_ids" })
+	@OneToMany(() => Ban, (ban: Ban) => ban.guild)
+	bans: Ban[];
+
 	@Column({ nullable: true })
 	banner?: string;
 
@@ -64,52 +78,57 @@ export class Guild extends BaseClass {
 	@Column({ nullable: true })
 	presence_count?: number; // users online
 
-	@RelationId((guild: Guild) => guild.members)
-	member_ids: string[];
-
-	@JoinColumn({ name: "member_ids" })
 	@OneToMany(() => Member, (member: Member) => member.guild)
 	members: Member[];
 
-	@RelationId((guild: Guild) => guild.roles)
-	role_ids: string[];
-
 	@JoinColumn({ name: "role_ids" })
 	@OneToMany(() => Role, (role: Role) => role.guild)
 	roles: Role[];
 
-	@RelationId((guild: Guild) => guild.channels)
-	channel_ids: string[];
-
 	@JoinColumn({ name: "channel_ids" })
 	@OneToMany(() => Channel, (channel: Channel) => channel.guild)
 	channels: Channel[];
 
-	@RelationId((guild: Guild) => guild.emojis)
-	emoji_ids: string[];
+	@Column({ nullable: true })
+	@RelationId((guild: Guild) => guild.template)
+	template_id: string;
+
+	@JoinColumn({ name: "template_id" })
+	@ManyToOne(() => Template)
+	template: Template;
 
 	@JoinColumn({ name: "emoji_ids" })
 	@OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild)
 	emojis: Emoji[];
 
-	@RelationId((guild: Guild) => guild.voice_states)
-	voice_state_ids: string[];
+	@JoinColumn({ name: "sticker_ids" })
+	@OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild)
+	stickers: Sticker[];
+
+	@JoinColumn({ name: "invite_ids" })
+	@OneToMany(() => Invite, (invite: Invite) => invite.guild)
+	invites: Invite[];
 
 	@JoinColumn({ name: "voice_state_ids" })
 	@OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild)
 	voice_states: VoiceState[];
 
+	@JoinColumn({ name: "webhook_ids" })
+	@OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild)
+	webhooks: Webhook[];
+
 	@Column({ nullable: true })
 	mfa_level?: number;
 
 	@Column()
 	name: string;
 
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.owner)
 	owner_id: string;
 
-	@JoinColumn({ name: "owner_id" })
-	@OneToOne(() => User)
+	@JoinColumn([{ name: "owner_id", referencedColumnName: "id" }])
+	@ManyToOne(() => User)
 	owner: User;
 
 	@Column({ nullable: true })
@@ -121,18 +140,20 @@ export class Guild extends BaseClass {
 	@Column({ nullable: true })
 	premium_tier?: number; // nitro boost level
 
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.public_updates_channel)
 	public_updates_channel_id: string;
 
 	@JoinColumn({ name: "public_updates_channel_id" })
-	@OneToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	public_updates_channel?: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.rules_channel)
 	rules_channel_id?: string;
 
 	@JoinColumn({ name: "rules_channel_id" })
-	@OneToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	rules_channel?: string;
 
 	@Column({ nullable: true })
@@ -141,11 +162,12 @@ export class Guild extends BaseClass {
 	@Column({ nullable: true })
 	splash?: string;
 
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.system_channel)
 	system_channel_id?: string;
 
 	@JoinColumn({ name: "system_channel_id" })
-	@OneToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	system_channel?: Channel;
 
 	@Column({ nullable: true })
@@ -154,11 +176,12 @@ export class Guild extends BaseClass {
 	@Column({ nullable: true })
 	unavailable?: boolean;
 
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.vanity_url)
 	vanity_url_code?: string;
 
 	@JoinColumn({ name: "vanity_url_code" })
-	@OneToOne(() => Invite)
+	@ManyToOne(() => Invite)
 	vanity_url?: Invite;
 
 	@Column({ nullable: true })
@@ -176,11 +199,12 @@ export class Guild extends BaseClass {
 		}[];
 	};
 
+	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.widget_channel)
 	widget_channel_id?: string;
 
 	@JoinColumn({ name: "widget_channel_id" })
-	@OneToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	widget_channel?: Channel;
 
 	@Column({ nullable: true })
diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts
index d8c6d69c..01e22294 100644
--- a/util/src/entities/Invite.ts
+++ b/util/src/entities/Invite.ts
@@ -27,32 +27,36 @@ export class Invite extends BaseClass {
 	@Column()
 	expires_at: Date;
 
+	@Column({ nullable: true })
 	@RelationId((invite: Invite) => invite.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
+	@Column({ nullable: true })
 	@RelationId((invite: Invite) => invite.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	channel: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((invite: Invite) => invite.inviter)
 	inviter_id: string;
 
 	@JoinColumn({ name: "inviter_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	inviter: User;
 
+	@Column({ nullable: true })
 	@RelationId((invite: Invite) => invite.target_user)
 	target_user_id: string;
 
 	@JoinColumn({ name: "target_user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62
 
 	@Column({ nullable: true })
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index c5d289ef..d2d78bb9 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -15,27 +15,22 @@ import { Role } from "./Role";
 
 @Entity("members")
 export class Member extends BaseClass {
-	@RelationId((member: Member) => member.user)
-	user_id: string;
-
-	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@JoinColumn({ name: "id" })
+	@ManyToOne(() => User)
 	user: User;
 
+	@Column({ nullable: true })
 	@RelationId((member: Member) => member.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.members)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
 	@Column({ nullable: true })
 	nick?: string;
 
-	@RelationId((member: Member) => member.roles)
-	role_ids: string[];
-
-	@JoinTable()
+	@JoinTable({ name: "member_roles" })
 	@ManyToMany(() => Role)
 	roles: Role[];
 
@@ -62,7 +57,7 @@ export class Member extends BaseClass {
 	read_state: Record<string, string | null>;
 
 	static async IsInGuildOrFail(user_id: string, guild_id: string) {
-		if (await Member.count({ id: user_id, guild_id })) return true;
+		if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true;
 		throw new HTTPError("You are not member of this guild", 403);
 	}
 
@@ -99,46 +94,54 @@ export class Member extends BaseClass {
 
 	static async addRole(user_id: string, guild_id: string, role_id: string) {
 		const [member] = await Promise.all([
+			// @ts-ignore
 			Member.findOneOrFail({
 				where: { id: user_id, guild_id: guild_id },
-				relations: ["user"], // we don't want to load  the role objects just the ids
+				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 }),
 		]);
-		member.role_ids.push(role_id);
-		member.save();
+		member.roles.push(new Role({ id: role_id }));
 
-		await emitEvent({
-			event: "GUILD_MEMBER_UPDATE",
-			data: {
+		await Promise.all([
+			member.save(),
+			emitEvent({
+				event: "GUILD_MEMBER_UPDATE",
+				data: {
+					guild_id: guild_id,
+					user: member.user,
+					roles: member.roles.map((x) => x.id),
+				},
 				guild_id: guild_id,
-				user: member.user,
-				roles: member.role_ids,
-			},
-			guild_id: guild_id,
-		} as GuildMemberUpdateEvent);
+			} as GuildMemberUpdateEvent),
+		]);
 	}
 
 	static async removeRole(user_id: string, guild_id: string, role_id: string) {
 		const [member] = await Promise.all([
+			// @ts-ignore
 			Member.findOneOrFail({
 				where: { id: user_id, guild_id: guild_id },
-				relations: ["user"], // we don't want to load  the role objects just the ids
+				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 }),
 		]);
-		member.role_ids.remove(role_id);
-		member.save();
+		member.roles = member.roles.filter((x) => x.id == role_id);
 
-		await emitEvent({
-			event: "GUILD_MEMBER_UPDATE",
-			data: {
+		await Promise.all([
+			member.save(),
+			emitEvent({
+				event: "GUILD_MEMBER_UPDATE",
+				data: {
+					guild_id: guild_id,
+					user: member.user,
+					roles: member.roles.map((x) => x.id),
+				},
 				guild_id: guild_id,
-				user: member.user,
-				roles: member.role_ids,
-			},
-			guild_id: guild_id,
-		} as GuildMemberUpdateEvent);
+			} as GuildMemberUpdateEvent),
+		]);
 	}
 
 	static async changeNickname(user_id: string, guild_id: string, nickname: string) {
@@ -175,11 +178,14 @@ export class Member extends BaseClass {
 			throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
 		}
 
-		const guild = await Guild.findOneOrFail(guild_id, {
+		const guild = await Guild.findOneOrFail({
+			where: {
+				id: guild_id,
+			},
 			relations: ["channels", "emojis", "members", "roles", "stickers"],
 		});
 
-		if (await Member.count({ id: user.id, guild_id }))
+		if (await Member.count({ id: user.id, guild: { id: guild_id } }))
 			throw new HTTPError("You are already a member of this guild", 400);
 
 		const member = {
@@ -194,23 +200,23 @@ export class Member extends BaseClass {
 			pending: false,
 		};
 		// @ts-ignore
-		guild.joined_at = member.joined_at;
+		guild.joined_at = member.joined_at.toISOString();
 
 		await Promise.all([
-			new Member({
+			Member.insert({
 				...member,
+				roles: undefined,
 				read_state: {},
 				settings: {
 					channel_overrides: [],
 					message_notifications: 0,
 					mobile_push: true,
-					mute_config: null,
 					muted: false,
 					suppress_everyone: false,
 					suppress_roles: false,
 					version: 0,
 				},
-			}).save(),
+			}),
 			Guild.increment({ id: guild_id }, "member_count", 1),
 			emitEvent({
 				event: "GUILD_MEMBER_ADD",
@@ -223,7 +229,7 @@ export class Member extends BaseClass {
 			} as GuildMemberAddEvent),
 			emitEvent({
 				event: "GUILD_CREATE",
-				data: guild,
+				data: { ...guild, members: [...guild.members, member] },
 				user_id,
 			} as GuildCreateEvent),
 		]);
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 43d0f9d0..542b2b55 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -9,8 +9,10 @@ import {
 	CreateDateColumn,
 	Entity,
 	JoinColumn,
+	JoinTable,
 	ManyToMany,
 	ManyToOne,
+	OneToMany,
 	RelationId,
 	UpdateDateColumn,
 } from "typeorm";
@@ -18,6 +20,7 @@ import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Webhook } from "./Webhook";
 import { Sticker } from "./Sticker";
+import { Attachment } from "./Attachment";
 
 export enum MessageType {
 	DEFAULT = 0,
@@ -44,46 +47,52 @@ export class Message extends BaseClass {
 	@Column()
 	id: string;
 
+	@Column({ nullable: true })
 	@RelationId((message: Message) => message.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	channel: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((message: Message) => message.guild)
 	guild_id?: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild?: Guild;
 
+	@Column({ nullable: true })
 	@RelationId((message: Message) => message.author)
 	author_id: string;
 
-	@JoinColumn({ name: "author_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@JoinColumn({ name: "author_id", referencedColumnName: "id" })
+	@ManyToOne(() => User)
 	author?: User;
 
+	@Column({ nullable: true })
 	@RelationId((message: Message) => message.member)
 	member_id: string;
 
 	@JoinColumn({ name: "member_id" })
-	@ManyToOne(() => Member, (member: Member) => member.id)
+	@ManyToOne(() => Member)
 	member?: Member;
 
+	@Column({ nullable: true })
 	@RelationId((message: Message) => message.webhook)
 	webhook_id: string;
 
 	@JoinColumn({ name: "webhook_id" })
-	@ManyToOne(() => Webhook, (webhook: Webhook) => webhook.id)
+	@ManyToOne(() => Webhook)
 	webhook?: Webhook;
 
+	@Column({ nullable: true })
 	@RelationId((message: Message) => message.application)
 	application_id: string;
 
 	@JoinColumn({ name: "application_id" })
-	@ManyToOne(() => Application, (application: Application) => application.id)
+	@ManyToOne(() => Application)
 	application?: Application;
 
 	@Column({ nullable: true })
@@ -103,29 +112,25 @@ export class Message extends BaseClass {
 	@Column({ nullable: true })
 	mention_everyone?: boolean;
 
-	@RelationId((message: Message) => message.mentions)
-	mention_user_ids: string[];
-
-	@JoinColumn({ name: "mention_user_ids" })
-	@ManyToMany(() => User, (user: User) => user.id)
+	@JoinTable({ name: "message_user_mentions" })
+	@ManyToMany(() => User)
 	mentions: User[];
 
-	@RelationId((message: Message) => message.mention_roles)
-	mention_role_ids: string[];
-
-	@JoinColumn({ name: "mention_role_ids" })
-	@ManyToMany(() => Role, (role: Role) => role.id)
+	@JoinTable({ name: "message_role_mentions" })
+	@ManyToMany(() => Role)
 	mention_roles: Role[];
 
-	@RelationId((message: Message) => message.mention_channels)
-	mention_channel_ids: string[];
-
-	@JoinColumn({ name: "mention_channel_ids" })
-	@ManyToMany(() => Channel, (channel: Channel) => channel.id)
+	@JoinTable({ name: "message_channel_mentions" })
+	@ManyToMany(() => Channel)
 	mention_channels: Channel[];
 
-	@Column({ type: "simple-json" })
-	attachments: Attachment[];
+	@JoinTable({ name: "message_stickers" })
+	@ManyToMany(() => Sticker)
+	sticker_items?: Sticker[];
+
+	@JoinColumn({ name: "attachment_ids" })
+	@OneToMany(() => Attachment, (attachment: Attachment) => attachment.message)
+	attachments?: Attachment[];
 
 	@Column({ type: "simple-json" })
 	embeds: Embed[];
@@ -150,14 +155,6 @@ export class Message extends BaseClass {
 
 	@Column({ nullable: true })
 	flags?: string;
-
-	@RelationId((message: Message) => message.stickers)
-	sticker_ids: string[];
-
-	@JoinColumn({ name: "sticker_ids" })
-	@ManyToMany(() => Sticker, (sticker: Sticker) => sticker.id)
-	stickers?: Sticker[];
-
 	@Column({ type: "simple-json", nullable: true })
 	message_reference?: {
 		message_id: string;
@@ -166,7 +163,7 @@ export class Message extends BaseClass {
 	};
 
 	@JoinColumn({ name: "message_reference_id" })
-	@ManyToOne(() => Message, (message: Message) => message.id)
+	@ManyToOne(() => Message)
 	referenced_message?: Message;
 
 	@Column({ type: "simple-json", nullable: true })
@@ -178,8 +175,8 @@ export class Message extends BaseClass {
 		// user: User; // TODO: autopopulate user
 	};
 
-	@Column({ type: "simple-json" })
-	components: MessageComponent[];
+	@Column({ type: "simple-json", nullable: true })
+	components?: MessageComponent[];
 }
 
 export interface MessageComponent {
@@ -198,17 +195,6 @@ export enum MessageComponentType {
 	Button = 2,
 }
 
-export interface Attachment {
-	id: string; // attachment id
-	filename: string; // name of file attached
-	size: number; // size of file in bytes
-	url: string; // source url of file
-	proxy_url: string; // a proxied url of file
-	height?: number; // height of file (if image)
-	width?: number; // width of file (if image)
-	content_type?: string;
-}
-
 export interface Embed {
 	title?: string; //title of embed
 	type?: EmbedType; // type of embed (always "rich" for webhook embeds)
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index 650ee4c3..8dd05b21 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -10,25 +10,28 @@ import { User } from "./User";
 
 @Entity("read_states")
 export class ReadState extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((read_state: ReadState) => read_state.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	channel: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((read_state: ReadState) => read_state.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	user: User;
 
+	@Column({ nullable: true })
 	@RelationId((read_state: ReadState) => read_state.last_message)
 	last_message_id: string;
 
 	@JoinColumn({ name: "last_message_id" })
-	@ManyToOne(() => Message, (message: Message) => message.id)
+	@ManyToOne(() => Message)
 	last_message?: Message;
 
 	@Column({ nullable: true })
diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts
new file mode 100644
index 00000000..75d5b94d
--- /dev/null
+++ b/util/src/entities/Recipient.ts
@@ -0,0 +1,19 @@
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+
+@Entity("recipients")
+export class Recipient extends BaseClass {
+	@Column()
+	@RelationId((recipient: Recipient) => recipient.channel)
+	channel_id: string;
+
+	@JoinColumn({ name: "channel_id" })
+	@ManyToOne(() => require("./Channel").Channel)
+	channel: import("./Channel").Channel;
+
+	@JoinColumn({ name: "id" })
+	@ManyToOne(() => require("./User").User)
+	user: import("./User").User;
+
+	// TODO: settings/mute/nick/added at/encryption keys/read_state
+}
diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts
index cf3624bf..5935f5b6 100644
--- a/util/src/entities/Relationship.ts
+++ b/util/src/entities/Relationship.ts
@@ -11,11 +11,12 @@ export enum RelationshipType {
 
 @Entity("relationships")
 export class Relationship extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((relationship: Relationship) => relationship.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.relationships)
+	@ManyToOne(() => User)
 	user: User;
 
 	@Column({ nullable: true })
diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts
index be8c731d..33c8d272 100644
--- a/util/src/entities/Role.ts
+++ b/util/src/entities/Role.ts
@@ -5,11 +5,12 @@ import { Guild } from "./Guild";
 
 @Entity("roles")
 export class Role extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((role: Role) => role.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
 	@Column()
diff --git a/util/src/entities/Sticker.ts b/util/src/entities/Sticker.ts
index 12394207..7730a86a 100644
--- a/util/src/entities/Sticker.ts
+++ b/util/src/entities/Sticker.ts
@@ -31,7 +31,7 @@ export class Sticker extends BaseClass {
 	guild_id?: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild?: Guild;
 
 	@Column({ type: "simple-enum", enum: StickerType })
diff --git a/util/src/entities/Team.ts b/util/src/entities/Team.ts
index b37f368c..beb8bf68 100644
--- a/util/src/entities/Team.ts
+++ b/util/src/entities/Team.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { TeamMember } from "./TeamMember";
 import { User } from "./User";
@@ -8,20 +8,18 @@ export class Team extends BaseClass {
 	@Column({ nullable: true })
 	icon?: string;
 
-	@RelationId((team: Team) => team.members)
-	member_ids: string[];
-
 	@JoinColumn({ name: "member_ids" })
-	@ManyToMany(() => TeamMember, (member: TeamMember) => member.id)
+	@OneToMany(() => TeamMember, (member: TeamMember) => member.team)
 	members: TeamMember[];
 
 	@Column()
 	name: string;
 
+	@Column({ nullable: true })
 	@RelationId((team: Team) => team.owner_user)
 	owner_user_id: string;
 
 	@JoinColumn({ name: "owner_user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	owner_user: User;
 }
diff --git a/util/src/entities/TeamMember.ts b/util/src/entities/TeamMember.ts
index d4c95397..6b184d08 100644
--- a/util/src/entities/TeamMember.ts
+++ b/util/src/entities/TeamMember.ts
@@ -15,17 +15,19 @@ export class TeamMember extends BaseClass {
 	@Column({ type: "simple-array" })
 	permissions: string[];
 
+	@Column({ nullable: true })
 	@RelationId((member: TeamMember) => member.team)
 	team_id: string;
 
 	@JoinColumn({ name: "team_id" })
-	@ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.id)
+	@ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members)
 	team: import("./Team").Team;
 
+	@Column({ nullable: true })
 	@RelationId((member: TeamMember) => member.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	user: User;
 }
diff --git a/util/src/entities/Template.ts b/util/src/entities/Template.ts
index 2d8c9b6c..76f77ba6 100644
--- a/util/src/entities/Template.ts
+++ b/util/src/entities/Template.ts
@@ -17,11 +17,12 @@ export class Template extends BaseClass {
 	@Column({ nullable: true })
 	usage_count?: number;
 
+	@Column({ nullable: true })
 	@RelationId((template: Template) => template.creator)
 	creator_id: string;
 
 	@JoinColumn({ name: "creator_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	creator: User;
 
 	@Column()
@@ -30,11 +31,12 @@ export class Template extends BaseClass {
 	@Column()
 	updated_at: Date;
 
+	@Column({ nullable: true })
 	@RelationId((template: Template) => template.source_guild)
 	source_guild_id: string;
 
 	@JoinColumn({ name: "source_guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	source_guild: Guild;
 
 	@Column({ type: "simple-json" })
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 73afba67..39f654be 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -1,9 +1,10 @@
-import { Column, Entity, FindOneOptions, JoinColumn, OneToMany, RelationId } from "typeorm";
+import { Column, Entity, FindOneOptions, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { BitField } from "../util/BitField";
 import { Relationship } from "./Relationship";
 import { ConnectedAccount } from "./ConnectedAccount";
 import { HTTPError } from "lambert-server";
+import { Channel } from "./Channel";
 
 type PublicUserKeys =
 	| "username"
@@ -105,16 +106,10 @@ export class User extends BaseClass {
 	@Column()
 	public_flags: string;
 
-	@RelationId((user: User) => user.relationships)
-	relationship_ids: string[]; // array of guild ids the user is part of
-
 	@JoinColumn({ name: "relationship_ids" })
 	@OneToMany(() => Relationship, (relationship: Relationship) => relationship.user, { cascade: true })
 	relationships: Relationship[];
 
-	@RelationId((user: User) => user.connected_accounts)
-	connected_account_ids: string[]; // array of guild ids the user is part of
-
 	@JoinColumn({ name: "connected_account_ids" })
 	@OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user)
 	connected_accounts: ConnectedAccount[];
@@ -126,16 +121,19 @@ export class User extends BaseClass {
 	};
 
 	@Column({ type: "simple-array" })
-	fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts
+	fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts
 
 	@Column({ type: "simple-json" })
 	settings: UserSettings;
 
 	static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
-		const user = await User.findOne(user_id, {
-			...opts,
-			select: [...PublicUserProjection, ...(opts?.select || [])],
-		});
+		const user = await User.findOne(
+			{ id: user_id },
+			{
+				...opts,
+				select: [...PublicUserProjection, ...(opts?.select || [])],
+			}
+		);
 		if (!user) throw new HTTPError("User not found", 404);
 		return user;
 	}
diff --git a/util/src/entities/VoiceState.ts b/util/src/entities/VoiceState.ts
index e9d3dfa2..c5040cf1 100644
--- a/util/src/entities/VoiceState.ts
+++ b/util/src/entities/VoiceState.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Guild } from "./Guild";
@@ -6,25 +6,28 @@ import { User } from "./User";
 
 @Entity("voice_states")
 export class VoiceState extends BaseClass {
+	@Column({ nullable: true })
 	@RelationId((voice_state: VoiceState) => voice_state.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild?: Guild;
 
+	@Column({ nullable: true })
 	@RelationId((voice_state: VoiceState) => voice_state.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	channel: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((voice_state: VoiceState) => voice_state.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	user: User;
 
 	@Column()
diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts
index a75cb959..12ba0d08 100644
--- a/util/src/entities/Webhook.ts
+++ b/util/src/entities/Webhook.ts
@@ -27,38 +27,43 @@ export class Webhook extends BaseClass {
 	@Column({ nullable: true })
 	token?: string;
 
+	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	guild: Guild;
 
+	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
-	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	@ManyToOne(() => Channel)
 	channel: Channel;
 
+	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.application)
 	application_id: string;
 
 	@JoinColumn({ name: "application_id" })
-	@ManyToOne(() => Application, (application: Application) => application.id)
+	@ManyToOne(() => Application)
 	application: Application;
 
+	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
-	@ManyToOne(() => User, (user: User) => user.id)
+	@ManyToOne(() => User)
 	user: User;
 
+	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.guild)
 	source_guild_id: string;
 
 	@JoinColumn({ name: "source_guild_id" })
-	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	@ManyToOne(() => Guild)
 	source_guild: Guild;
 }
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index e0246a10..aa37ae2e 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -1,4 +1,5 @@
 export * from "./Application";
+export * from "./Attachment";
 export * from "./AuditLog";
 export * from "./Ban";
 export * from "./BaseClass";
@@ -12,6 +13,7 @@ export * from "./Member";
 export * from "./Message";
 export * from "./RateLimit";
 export * from "./ReadState";
+export * from "./Recipient";
 export * from "./Relationship";
 export * from "./Role";
 export * from "./Sticker";
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index 814a8beb..7ea1bd49 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -89,14 +89,7 @@ export interface ReadyEventData {
 	};
 	merged_members?: Omit<Member, "settings" | "user">[][];
 	// probably all users who the user is in contact with
-	users?: {
-		avatar: string | null;
-		discriminator: string;
-		id: string;
-		username: string;
-		bot: boolean;
-		public_flags: string;
-	}[];
+	users?: PublicUser[];
 }
 
 export interface ReadyEvent extends Event {
@@ -130,7 +123,9 @@ export interface ChannelPinsUpdateEvent extends Event {
 
 export interface GuildCreateEvent extends Event {
 	event: "GUILD_CREATE";
-	data: Guild;
+	data: Guild & {
+		joined_at: Date;
+	};
 }
 
 export interface GuildUpdateEvent extends Event {
diff --git a/util/src/tes.ts b/util/src/tes.ts
index b469f1d8..e326dee1 100644
--- a/util/src/tes.ts
+++ b/util/src/tes.ts
@@ -5,10 +5,14 @@ import { initDatabase } from "./util";
 
 initDatabase().then(async (x) => {
 	try {
-		const user = await new User(
-			{ guilds: [], discriminator: "1", username: "test", flags: "0", public_flags: "0" },
-			{ id: "0" }
-		).save();
+		const user = await new User({
+			guilds: [],
+			discriminator: "1",
+			username: "test",
+			flags: "0",
+			public_flags: "0",
+			id: "0",
+		}).save();
 
 		user.relationships = [new Relationship({ type: RelationshipType.friends })];
 
diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
index 508a8901..1ec71ad0 100644
--- a/util/src/util/Config.ts
+++ b/util/src/util/Config.ts
@@ -7,7 +7,7 @@ var config: ConfigEntity;
 export const Config = {
 	init: async function init() {
 		if (config) return config;
-		config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({}, { id: "0" });
+		config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({ id: "0" });
 
 		return this.set((config.value || {}).merge(DefaultConfigOptions));
 	},
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index ab6aa795..89b316ee 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -220,13 +220,14 @@ 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_id, id: user_id }, relations: ["roles"] });
+		member = await Member.findOneOrFail({ where: { guild: guild_id, id: user_id }, relations: ["roles"] });
 	}
 
+	// TODO: remove guild.roles and convert recipient_ids to recipients
 	var permission = Permissions.finalPermission({
 		user: {
 			id: user_id,
-			roles: member?.role_ids || [],
+			roles: member?.roles.map((x) => x.id) || [],
 		},
 		guild: {
 			roles: member?.roles || [],
@@ -234,7 +235,7 @@ export async function getPermission(user_id?: string, guild_id?: string, channel
 		channel: {
 			overwrites: channel?.permission_overwrites,
 			owner_id: channel?.owner_id,
-			recipient_ids: channel?.recipient_ids,
+			recipient_ids: channel?.recipients?.map((x) => x.id),
 		},
 	});