summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/config/types/RegionConfiguration.ts2
-rw-r--r--src/util/entities/Attachment.ts6
-rw-r--r--src/util/entities/AuditLog.ts7
-rw-r--r--src/util/entities/BackupCodes.ts2
-rw-r--r--src/util/entities/Ban.ts9
-rw-r--r--src/util/entities/Channel.ts21
-rw-r--r--src/util/entities/ConnectedAccount.ts5
-rw-r--r--src/util/entities/Emoji.ts7
-rw-r--r--src/util/entities/Guild.ts52
-rw-r--r--src/util/entities/Invite.ts6
-rw-r--r--src/util/entities/Member.ts10
-rw-r--r--src/util/entities/Message.ts24
-rw-r--r--src/util/entities/Migration.ts1
-rw-r--r--src/util/entities/Note.ts7
-rw-r--r--src/util/entities/RateLimit.ts1
-rw-r--r--src/util/entities/ReadState.ts7
-rw-r--r--src/util/entities/Recipient.ts9
-rw-r--r--src/util/entities/Relationship.ts7
-rw-r--r--src/util/entities/Role.ts5
-rw-r--r--src/util/entities/Session.ts2
-rw-r--r--src/util/entities/Sticker.ts8
-rw-r--r--src/util/entities/StickerPack.ts4
-rw-r--r--src/util/entities/Team.ts4
-rw-r--r--src/util/entities/TeamMember.ts8
-rw-r--r--src/util/entities/Template.ts7
-rw-r--r--src/util/entities/User.ts13
-rw-r--r--src/util/entities/VoiceState.ts13
-rw-r--r--src/util/entities/Webhook.ts13
-rw-r--r--src/util/migrations/mariadb/1660258393551-CodeCleanup3.ts231
-rw-r--r--src/util/migrations/mariadb/1660260587556-CodeCleanup4.ts38
-rw-r--r--src/util/migrations/mariadb/1660265930624-CodeCleanup5.ts52
-rw-r--r--src/util/plugin/Plugin.ts13
-rw-r--r--src/util/plugin/PluginLoader.ts39
-rw-r--r--src/util/plugin/PluginManifest.ts9
-rw-r--r--src/util/plugin/index.ts3
-rw-r--r--src/util/schemas/SelectProtocolSchema.ts19
-rw-r--r--src/util/schemas/Validator.ts54
-rw-r--r--src/util/schemas/VoiceIdentifySchema.ts12
-rw-r--r--src/util/schemas/VoiceVideoSchema.ts17
-rw-r--r--src/util/schemas/index.ts4
-rw-r--r--src/util/util/Database.ts1
-rw-r--r--src/util/util/NamingStrategy.ts12
-rw-r--r--src/util/util/imports/TypedEmitter.ts41
43 files changed, 686 insertions, 119 deletions
diff --git a/src/util/config/types/RegionConfiguration.ts b/src/util/config/types/RegionConfiguration.ts
index b4b8c4a3..418f46f1 100644
--- a/src/util/config/types/RegionConfiguration.ts
+++ b/src/util/config/types/RegionConfiguration.ts
@@ -7,7 +7,7 @@ export class RegionConfiguration {
 		{
 			id: "fosscord",
 			name: "Fosscord",
-			endpoint: "127.0.0.1:3004",
+			endpoint: undefined,
 			vip: false,
 			custom: false,
 			deprecated: false
diff --git a/src/util/entities/Attachment.ts b/src/util/entities/Attachment.ts
index 8392f415..395e695a 100644
--- a/src/util/entities/Attachment.ts
+++ b/src/util/entities/Attachment.ts
@@ -1,7 +1,9 @@
-import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { URL } from "url";
 import { deleteFile } from "../util/cdn";
 import { BaseClass } from "./BaseClass";
+import { Message } from "./Message";
 
 @Entity("attachments")
 export class Attachment extends BaseClass {
@@ -34,7 +36,7 @@ export class Attachment extends BaseClass {
 	@ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, {
 		onDelete: "CASCADE"
 	})
-	message: import("./Message").Message;
+	message: Relation<Message>;
 
 	@BeforeRemove()
 	onDelete() {
diff --git a/src/util/entities/AuditLog.ts b/src/util/entities/AuditLog.ts
index 6f394f42..f8c65145 100644
--- a/src/util/entities/AuditLog.ts
+++ b/src/util/entities/AuditLog.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { ChannelPermissionOverwrite } from "./Channel";
 import { User } from "./User";
@@ -97,7 +98,7 @@ export enum AuditLogEvents {
 export class AuditLog extends BaseClass {
 	@JoinColumn({ name: "target_id" })
 	@ManyToOne(() => User)
-	target?: User;
+	target?: Relation<User>;
 
 	@Column({ nullable: true })
 	@RelationId((auditlog: AuditLog) => auditlog.user)
@@ -105,7 +106,7 @@ export class AuditLog extends BaseClass {
 
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
-	user: User;
+	user: Relation<User>;
 
 	@Column({ type: "int" })
 	action_type: AuditLogEvents;
diff --git a/src/util/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts
index 503b1dbd..79b60a5e 100644
--- a/src/util/entities/BackupCodes.ts
+++ b/src/util/entities/BackupCodes.ts
@@ -6,7 +6,7 @@ import { User } from "./User";
 export class BackupCode extends BaseClass {
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, { onDelete: "CASCADE" })
-	user: User;
+	user: Relation<User>;
 
 	@Column()
 	code: string;
diff --git a/src/util/entities/Ban.ts b/src/util/entities/Ban.ts
index 27c75278..e7daaf2a 100644
--- a/src/util/entities/Ban.ts
+++ b/src/util/entities/Ban.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { User } from "./User";
@@ -13,7 +14,7 @@ export class Ban extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	@Column({ nullable: true })
 	@RelationId((ban: Ban) => ban.guild)
@@ -23,7 +24,7 @@ export class Ban extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((ban: Ban) => ban.executor)
@@ -31,7 +32,7 @@ export class Ban extends BaseClass {
 
 	@JoinColumn({ name: "executor_id" })
 	@ManyToOne(() => User)
-	executor: User;
+	executor: Relation<User>;
 
 	@Column()
 	ip: string;
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index b17fdba0..17a077ba 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -56,7 +56,7 @@ export class Channel extends BaseClass {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	recipients?: Recipient[];
+	recipients?: Relation<Recipient[]>;
 
 	@Column({ nullable: true })
 	last_message_id: string;
@@ -69,7 +69,7 @@ export class Channel extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((channel: Channel) => channel.parent)
@@ -77,7 +77,7 @@ export class Channel extends BaseClass {
 
 	@JoinColumn({ name: "parent_id" })
 	@ManyToOne(() => Channel)
-	parent?: Channel;
+	parent?: Relation<Channel>;
 
 	// for group DMs and owned custom channel types
 	@Column({ nullable: true })
@@ -86,7 +86,7 @@ export class Channel extends BaseClass {
 
 	@JoinColumn({ name: "owner_id" })
 	@ManyToOne(() => User)
-	owner: User;
+	owner: Relation<User>;
 
 	@Column({ nullable: true })
 	last_pin_timestamp?: number;
@@ -113,16 +113,13 @@ export class Channel extends BaseClass {
 	nsfw?: boolean;
 
 	@Column({ nullable: true })
-	rate_limit_per_user?: number;
-
-	@Column({ nullable: true })
 	topic?: string;
 
 	@OneToMany(() => Invite, (invite: Invite) => invite.channel, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	invites?: Invite[];
+	invites?: Relation<Invite[]>;
 
 	@Column({ nullable: true })
 	retention_policy_id?: string;
@@ -131,25 +128,25 @@ export class Channel extends BaseClass {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	messages?: Message[];
+	messages?: Relation<Message[]>;
 
 	@OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	voice_states?: VoiceState[];
+	voice_states?: Relation<VoiceState[]>;
 
 	@OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	read_states?: ReadState[];
+	read_states?: Relation<ReadState[]>;
 
 	@OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	webhooks?: Webhook[];
+	webhooks?: Relation<Webhook[]>;
 
 	@Column({ nullable: true })
 	flags?: number = 0;
diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts
index 018b3995..a74b7a43 100644
--- a/src/util/entities/ConnectedAccount.ts
+++ b/src/util/entities/ConnectedAccount.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
@@ -14,7 +15,7 @@ export class ConnectedAccount extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	@Column({ select: false })
 	access_token: string;
diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts
index a2552995..4a453afe 100644
--- a/src/util/entities/Emoji.ts
+++ b/src/util/entities/Emoji.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { User } from ".";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
@@ -18,7 +19,7 @@ export class Emoji extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((emoji: Emoji) => emoji.user)
@@ -26,7 +27,7 @@ export class Emoji extends BaseClass {
 
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User)
-	user: User;
+	user: Relation<User>;
 
 	@Column()
 	managed: boolean;
diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts
index 015c6d04..9efb7e74 100644
--- a/src/util/entities/Guild.ts
+++ b/src/util/entities/Guild.ts
@@ -41,8 +41,8 @@ export class Guild extends BaseClass {
 	afk_channel_id?: string;
 
 	@JoinColumn({ name: "afk_channel_id" })
-	@ManyToOne(() => Channel)
-	afk_channel?: Channel;
+	@ManyToOne(() => Channel, { nullable: true })
+	afk_channel?: Relation<Channel>;
 
 	@Column({ nullable: true })
 	afk_timeout?: number = Config.get().defaults.guild.afkTimeout;
@@ -57,7 +57,7 @@ export class Guild extends BaseClass {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	bans: Ban[];
+	bans: Relation<Ban[]>;
 
 	@Column({ nullable: true })
 	banner?: string;
@@ -107,7 +107,7 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	members: Member[];
+	members: Relation<Member[]>;
 
 	@JoinColumn({ name: "role_ids" })
 	@OneToMany(() => Role, (role: Role) => role.guild, {
@@ -115,14 +115,14 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	roles: Role[];
+	roles: Relation<Role[]>;
 
 	@JoinColumn({ name: "channel_ids" })
 	@OneToMany(() => Channel, (channel: Channel) => channel.guild, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	channels: Channel[];
+	channels: Relation<Channel[]>;
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.template)
@@ -130,7 +130,7 @@ export class Guild extends BaseClass {
 
 	@JoinColumn({ name: "template_id", referencedColumnName: "id" })
 	@ManyToOne(() => Template)
-	template: Template;
+	template: Relation<Template>;
 
 	@JoinColumn({ name: "emoji_ids" })
 	@OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, {
@@ -138,7 +138,7 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	emojis: Emoji[];
+	emojis: Relation<Emoji[]>;
 
 	@JoinColumn({ name: "sticker_ids" })
 	@OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, {
@@ -146,7 +146,7 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	stickers: Sticker[];
+	stickers: Relation<Sticker[]>;
 
 	@JoinColumn({ name: "invite_ids" })
 	@OneToMany(() => Invite, (invite: Invite) => invite.guild, {
@@ -154,7 +154,7 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	invites: Invite[];
+	invites: Relation<Invite[]>;
 
 	@JoinColumn({ name: "voice_state_ids" })
 	@OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, {
@@ -162,7 +162,7 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	voice_states: VoiceState[];
+	voice_states: Relation<VoiceState[]>;
 
 	@JoinColumn({ name: "webhook_ids" })
 	@OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, {
@@ -170,7 +170,7 @@ export class Guild extends BaseClass {
 		orphanedRowAction: "delete",
 		onDelete: "CASCADE"
 	})
-	webhooks: Webhook[];
+	webhooks: Relation<Webhook[]>;
 
 	@Column({ nullable: true })
 	mfa_level?: number;
@@ -183,8 +183,8 @@ export class Guild extends BaseClass {
 	owner_id?: string; // optional to allow for ownerless guilds
 
 	@JoinColumn({ name: "owner_id", referencedColumnName: "id" })
-	@ManyToOne(() => User)
-	owner?: User; // optional to allow for ownerless guilds
+	@ManyToOne(() => User, { nullable: true })
+	owner?: Relation<User>; // optional to allow for ownerless guilds
 
 	@Column({ nullable: true })
 	preferred_locale?: string;
@@ -200,16 +200,16 @@ export class Guild extends BaseClass {
 	public_updates_channel_id: string;
 
 	@JoinColumn({ name: "public_updates_channel_id" })
-	@ManyToOne(() => Channel)
-	public_updates_channel?: Channel;
+	@ManyToOne(() => Channel, { nullable: true })
+	public_updates_channel?: Relation<Channel>;
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.rules_channel)
 	rules_channel_id?: string;
 
 	@JoinColumn({ name: "rules_channel_id" })
-	@ManyToOne(() => Channel)
-	rules_channel?: string;
+	@ManyToOne(() => Channel, { nullable: true })
+	rules_channel?: Relation<Channel>;
 
 	@Column({ nullable: true })
 	region?: string;
@@ -222,8 +222,8 @@ export class Guild extends BaseClass {
 	system_channel_id?: string;
 
 	@JoinColumn({ name: "system_channel_id" })
-	@ManyToOne(() => Channel)
-	system_channel?: Channel;
+	@ManyToOne(() => Channel, { nullable: true })
+	system_channel?: Relation<Channel>;
 
 	@Column({ nullable: true })
 	system_channel_flags?: number;
@@ -251,8 +251,8 @@ export class Guild extends BaseClass {
 	widget_channel_id?: string;
 
 	@JoinColumn({ name: "widget_channel_id" })
-	@ManyToOne(() => Channel)
-	widget_channel?: Channel;
+	@ManyToOne(() => Channel, { nullable: true })
+	widget_channel?: Relation<Channel>;
 
 	@Column({ nullable: true })
 	widget_enabled?: boolean;
@@ -333,13 +333,13 @@ export class Guild extends BaseClass {
 
 		const ids = new Map();
 
-		body.channels.forEach((x) => {
-			if (x.id) {
-				ids.set(x.id, Snowflake.generate());
+		body.channels!.forEach((x) => {
+			if (x!.id) {
+				ids.set(x!.id, Snowflake.generate());
 			}
 		});
 
-		for (const channel of body.channels?.sort((a, b) => (a.parent_id ? 1 : -1))) {
+		for (const channel of body.channels!.sort((a, b) => (a.parent_id ? 1 : -1))) {
 			let id = ids.get(channel.id) || Snowflake.generate();
 
 			let parent_id = ids.get(channel.parent_id);
diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts
index f6ba85d7..383c932b 100644
--- a/src/util/entities/Invite.ts
+++ b/src/util/entities/Invite.ts
@@ -39,7 +39,7 @@ export class Invite extends BaseClassWithoutId {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((invite: Invite) => invite.channel)
@@ -49,7 +49,7 @@ export class Invite extends BaseClassWithoutId {
 	@ManyToOne(() => Channel, {
 		onDelete: "CASCADE"
 	})
-	channel: Channel;
+	channel: Relation<Channel>;
 
 	@Column({ nullable: true })
 	@RelationId((invite: Invite) => invite.inviter)
@@ -69,7 +69,7 @@ export class Invite extends BaseClassWithoutId {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62
+	target_user?: Relation<User>; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62
 
 	@Column({ nullable: true })
 	target_user_type?: number;
diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index 42a014d4..f5329481 100644
--- a/src/util/entities/Member.ts
+++ b/src/util/entities/Member.ts
@@ -40,7 +40,7 @@ export class Member extends BaseClassWithoutId {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	@Column()
 	@RelationId((member: Member) => member.guild)
@@ -50,7 +50,7 @@ export class Member extends BaseClassWithoutId {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column({ nullable: true })
 	nick?: string;
@@ -64,7 +64,7 @@ export class Member extends BaseClassWithoutId {
 		}
 	})
 	@ManyToMany(() => Role, { cascade: true })
-	roles: Role[];
+	roles: Relation<Role[]>;
 
 	@Column()
 	joined_at: Date;
@@ -106,7 +106,7 @@ export class Member extends BaseClassWithoutId {
 	}
 
 	static async removeFromGuild(user_id: string, guild_id: string) {
-		const guild = await Guild.findOneOrFail({ select: ["owner_id", "member_count"], where: { id: guild_id } });
+		const guild = await require("./Guild").Guild.findOneOrFail({ select: ["owner_id", "member_count"], 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"] });
 
@@ -224,7 +224,7 @@ export class Member extends BaseClassWithoutId {
 			throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
 		}
 
-		const guild = await Guild.findOneOrFail({
+		const guild = await require("./Guild").Guild.findOneOrFail({
 			where: {
 				id: guild_id
 			},
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index 8122b532..2b5e6f86 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -51,7 +51,7 @@ export class Message extends BaseClass {
 	@ManyToOne(() => Channel, {
 		onDelete: "CASCADE"
 	})
-	channel: Channel;
+	channel: Relation<Channel>;
 
 	@Column({ nullable: true })
 	@RelationId((message: Message) => message.guild)
@@ -61,7 +61,7 @@ export class Message extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild?: Guild;
+	guild?: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((message: Message) => message.author)
@@ -72,7 +72,7 @@ export class Message extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	author?: User;
+	author?: Relation<User>;
 
 	@Column({ nullable: true })
 	@RelationId((message: Message) => message.member)
@@ -82,7 +82,7 @@ export class Message extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	member?: Member;
+	member?: Relation<Member>;
 
 	@Column({ nullable: true })
 	@RelationId((message: Message) => message.webhook)
@@ -90,7 +90,7 @@ export class Message extends BaseClass {
 
 	@JoinColumn({ name: "webhook_id" })
 	@ManyToOne(() => Webhook)
-	webhook?: Webhook;
+	webhook?: Relation<Webhook>;
 
 	@Column({ nullable: true })
 	@RelationId((message: Message) => message.application)
@@ -98,7 +98,7 @@ export class Message extends BaseClass {
 
 	@JoinColumn({ name: "application_id" })
 	@ManyToOne(() => Application)
-	application?: Application;
+	application?: Relation<Application>;
 
 	@Column({ nullable: true })
 	content?: string;
@@ -118,25 +118,25 @@ export class Message extends BaseClass {
 
 	@JoinTable({ name: "message_user_mentions" })
 	@ManyToMany(() => User)
-	mentions: User[];
+	mentions: Relation<User[]>;
 
 	@JoinTable({ name: "message_role_mentions" })
 	@ManyToMany(() => Role)
-	mention_roles: Role[];
+	mention_roles: Relation<Role[]>;
 
 	@JoinTable({ name: "message_channel_mentions" })
 	@ManyToMany(() => Channel)
-	mention_channels: Channel[];
+	mention_channels: Relation<Channel[]>;
 
 	@JoinTable({ name: "message_stickers" })
 	@ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" })
-	sticker_items?: Sticker[];
+	sticker_items?: Relation<Sticker[]>;
 
 	@OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	attachments?: Attachment[];
+	attachments?: Relation<Attachment[]>;
 
 	@Column({ type: "simple-json" })
 	embeds: Embed[];
@@ -170,7 +170,7 @@ export class Message extends BaseClass {
 
 	@JoinColumn({ name: "message_reference_id" })
 	@ManyToOne(() => Message)
-	referenced_message?: Message;
+	referenced_message?: Relation<Message>;
 
 	@Column({ type: "simple-json", nullable: true })
 	interaction?: {
diff --git a/src/util/entities/Migration.ts b/src/util/entities/Migration.ts
index 626ec429..07fd25a6 100644
--- a/src/util/entities/Migration.ts
+++ b/src/util/entities/Migration.ts
@@ -1,3 +1,4 @@
+import "reflect-metadata";
 import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm";
 import { BaseClassWithoutId } from ".";
 
diff --git a/src/util/entities/Note.ts b/src/util/entities/Note.ts
index b3ac45ee..d6eefd63 100644
--- a/src/util/entities/Note.ts
+++ b/src/util/entities/Note.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, Unique } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, Unique } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
@@ -7,11 +8,11 @@ import { User } from "./User";
 export class Note extends BaseClass {
 	@JoinColumn({ name: "owner_id" })
 	@ManyToOne(() => User, { onDelete: "CASCADE" })
-	owner: User;
+	owner: Relation<User>;
 
 	@JoinColumn({ name: "target_id" })
 	@ManyToOne(() => User, { onDelete: "CASCADE" })
-	target: User;
+	target: Relation<User>;
 
 	@Column()
 	content: string;
diff --git a/src/util/entities/RateLimit.ts b/src/util/entities/RateLimit.ts
index f5916f6b..1f6c359c 100644
--- a/src/util/entities/RateLimit.ts
+++ b/src/util/entities/RateLimit.ts
@@ -1,3 +1,4 @@
+import "reflect-metadata";
 import { Column, Entity } from "typeorm";
 import { BaseClass } from "./BaseClass";
 
diff --git a/src/util/entities/ReadState.ts b/src/util/entities/ReadState.ts
index 77d2c08a..462a06f9 100644
--- a/src/util/entities/ReadState.ts
+++ b/src/util/entities/ReadState.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, Index, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { User } from "./User";
@@ -18,7 +19,7 @@ export class ReadState extends BaseClass {
 	@ManyToOne(() => Channel, {
 		onDelete: "CASCADE"
 	})
-	channel: Channel;
+	channel: Relation<Channel>;
 
 	@Column()
 	@RelationId((read_state: ReadState) => read_state.user)
@@ -28,7 +29,7 @@ export class ReadState extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	// fully read marker
 	@Column({ nullable: true })
diff --git a/src/util/entities/Recipient.ts b/src/util/entities/Recipient.ts
index fc9e629b..a1ae1e17 100644
--- a/src/util/entities/Recipient.ts
+++ b/src/util/entities/Recipient.ts
@@ -1,5 +1,8 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
+import { Channel } from "./Channel";
+import { User } from "./User";
 
 @Entity("recipients")
 export class Recipient extends BaseClass {
@@ -11,7 +14,7 @@ export class Recipient extends BaseClass {
 	@ManyToOne(() => require("./Channel").Channel, {
 		onDelete: "CASCADE"
 	})
-	channel: import("./Channel").Channel;
+	channel: Relation<Channel>;
 
 	@Column()
 	@RelationId((recipient: Recipient) => recipient.user)
@@ -21,7 +24,7 @@ export class Recipient extends BaseClass {
 	@ManyToOne(() => require("./User").User, {
 		onDelete: "CASCADE"
 	})
-	user: import("./User").User;
+	user: Relation<User>;
 
 	@Column({ default: false })
 	closed: boolean;
diff --git a/src/util/entities/Relationship.ts b/src/util/entities/Relationship.ts
index b55d9e64..c4525125 100644
--- a/src/util/entities/Relationship.ts
+++ b/src/util/entities/Relationship.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, Index, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
@@ -20,7 +21,7 @@ export class Relationship extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	from: User;
+	from: Relation<User>;
 
 	@Column({})
 	@RelationId((relationship: Relationship) => relationship.to)
@@ -30,7 +31,7 @@ export class Relationship extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	to: User;
+	to: Relation<User>;
 
 	@Column({ nullable: true })
 	nickname?: string;
diff --git a/src/util/entities/Role.ts b/src/util/entities/Role.ts
index b1fd9bb1..1c5e19cf 100644
--- a/src/util/entities/Role.ts
+++ b/src/util/entities/Role.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
@@ -13,7 +14,7 @@ export class Role extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column()
 	color: number;
diff --git a/src/util/entities/Session.ts b/src/util/entities/Session.ts
index 0cb4c309..bc9e6906 100644
--- a/src/util/entities/Session.ts
+++ b/src/util/entities/Session.ts
@@ -16,7 +16,7 @@ export class Session extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	//TODO check, should be 32 char long hex string
 	@Column({ nullable: false, select: false })
diff --git a/src/util/entities/Sticker.ts b/src/util/entities/Sticker.ts
index 69836e62..0879e064 100644
--- a/src/util/entities/Sticker.ts
+++ b/src/util/entities/Sticker.ts
@@ -34,11 +34,11 @@ export class Sticker extends BaseClass {
 	pack_id?: string;
 
 	@JoinColumn({ name: "pack_id" })
-	@ManyToOne(() => require("./StickerPack").StickerPack, {
+	@ManyToOne(() => StickerPack, {
 		onDelete: "CASCADE",
 		nullable: true
 	})
-	pack: import("./StickerPack").StickerPack;
+	pack: Relation<StickerPack>;
 
 	@Column({ nullable: true })
 	guild_id?: string;
@@ -47,7 +47,7 @@ export class Sticker extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild?: Guild;
+	guild?: Relation<Guild>;
 
 	@Column({ nullable: true })
 	user_id?: string;
@@ -56,7 +56,7 @@ export class Sticker extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user?: User;
+	user?: Relation<User>;
 
 	@Column({ type: "int" })
 	type: StickerType;
diff --git a/src/util/entities/StickerPack.ts b/src/util/entities/StickerPack.ts
index 4619af34..ca8b4cad 100644
--- a/src/util/entities/StickerPack.ts
+++ b/src/util/entities/StickerPack.ts
@@ -17,7 +17,7 @@ export class StickerPack extends BaseClass {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	stickers: Sticker[];
+	stickers: Relation<Sticker[]>;
 
 	// sku_id: string
 
@@ -27,5 +27,5 @@ export class StickerPack extends BaseClass {
 
 	@ManyToOne(() => Sticker, { nullable: true })
 	@JoinColumn()
-	cover_sticker?: Sticker;
+	cover_sticker?: Relation<Sticker>;
 }
diff --git a/src/util/entities/Team.ts b/src/util/entities/Team.ts
index 1d2d7002..48657930 100644
--- a/src/util/entities/Team.ts
+++ b/src/util/entities/Team.ts
@@ -12,7 +12,7 @@ export class Team extends BaseClass {
 	@OneToMany(() => TeamMember, (member: TeamMember) => member.team, {
 		orphanedRowAction: "delete"
 	})
-	members: TeamMember[];
+	members: Relation<TeamMember[]>;
 
 	@Column()
 	name: string;
@@ -23,5 +23,5 @@ export class Team extends BaseClass {
 
 	@JoinColumn({ name: "owner_user_id" })
 	@ManyToOne(() => User)
-	owner_user: User;
+	owner_user: Relation<User>;
 }
diff --git a/src/util/entities/TeamMember.ts b/src/util/entities/TeamMember.ts
index d11ebf95..e2950c65 100644
--- a/src/util/entities/TeamMember.ts
+++ b/src/util/entities/TeamMember.ts
@@ -1,5 +1,7 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
+import { Team } from "./Team";
 import { User } from "./User";
 
 export enum TeamMemberState {
@@ -23,7 +25,7 @@ export class TeamMember extends BaseClass {
 	@ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, {
 		onDelete: "CASCADE"
 	})
-	team: import("./Team").Team;
+	team: Relation<Team>;
 
 	@Column({ nullable: true })
 	@RelationId((member: TeamMember) => member.user)
@@ -33,5 +35,5 @@ export class TeamMember extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 }
diff --git a/src/util/entities/Template.ts b/src/util/entities/Template.ts
index 1d952283..bcd2b259 100644
--- a/src/util/entities/Template.ts
+++ b/src/util/entities/Template.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { User } from "./User";
@@ -23,7 +24,7 @@ export class Template extends BaseClass {
 
 	@JoinColumn({ name: "creator_id" })
 	@ManyToOne(() => User)
-	creator: User;
+	creator: Relation<User>;
 
 	@Column()
 	created_at: Date;
@@ -37,7 +38,7 @@ export class Template extends BaseClass {
 
 	@JoinColumn({ name: "source_guild_id" })
 	@ManyToOne(() => Guild)
-	source_guild: Guild;
+	source_guild: Relation<Guild>;
 
 	@Column({ type: "simple-json" })
 	serialized_source_guild: Guild;
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index 1237b676..760ef9c7 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -141,21 +141,21 @@ export class User extends BaseClass {
 	rights: string = Config.get().register.defaultRights; // Rights
 
 	@OneToMany(() => Session, (session: Session) => session.user)
-	sessions: Session[];
+	sessions: Relation<Session[]>;
 
 	@JoinColumn({ name: "relationship_ids" })
 	@OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	relationships: Relationship[];
+	relationships: Relation<Relationship[]>;
 
 	@JoinColumn({ name: "connected_account_ids" })
 	@OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, {
 		cascade: true,
 		orphanedRowAction: "delete"
 	})
-	connected_accounts: ConnectedAccount[];
+	connected_accounts: Relation<ConnectedAccount[]>;
 
 	@Column({ type: "simple-json", select: false })
 	data: {
@@ -264,6 +264,8 @@ export class User extends BaseClass {
 		// appearently discord doesn't save the date of birth and just calculate if nsfw is allowed
 		// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
 		const language = req?.language === "en" ? "en-US" : req?.language || "en-US";
+		const settings = new UserSettings();
+		settings.locale = language;
 
 		const user = OrmUtils.mergeDeep(new User(), {
 			//required:
@@ -280,11 +282,14 @@ export class User extends BaseClass {
 
 		//await (user.settings as UserSettings).save();
 		await user.save();
+		await user.settings.save();
 
 		setImmediate(async () => {
 			if (Config.get().guild.autoJoin.enabled) {
 				for (const guild of Config.get().guild.autoJoin.guilds || []) {
-					await Member.addToGuild(user.id, guild).catch((e) => {});
+					await require("./Member")
+						.Member.addToGuild(user.id, guild)
+						.catch((e: any) => {});
 				}
 			}
 		});
diff --git a/src/util/entities/VoiceState.ts b/src/util/entities/VoiceState.ts
index baf2c687..55fac313 100644
--- a/src/util/entities/VoiceState.ts
+++ b/src/util/entities/VoiceState.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Guild } from "./Guild";
@@ -16,7 +17,7 @@ export class VoiceState extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild?: Guild;
+	guild?: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((voice_state: VoiceState) => voice_state.channel)
@@ -26,7 +27,7 @@ export class VoiceState extends BaseClass {
 	@ManyToOne(() => Channel, {
 		onDelete: "CASCADE"
 	})
-	channel: Channel;
+	channel: Relation<Channel>;
 
 	@Column({ nullable: true })
 	@RelationId((voice_state: VoiceState) => voice_state.user)
@@ -36,20 +37,20 @@ export class VoiceState extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	// @JoinColumn([{ name: "user_id", referencedColumnName: "id" },{ name: "guild_id", referencedColumnName: "guild_id" }])
 	// @ManyToOne(() => Member, {
 	// 	onDelete: "CASCADE",
 	// })
 	//TODO find a way to make it work without breaking Guild.voice_states
-	member: Member;
+	member: Relation<Member>;
 
 	@Column()
 	session_id: string;
 
 	@Column({ nullable: true })
-	token: string;
+	token?: string;
 
 	@Column()
 	deaf: boolean;
diff --git a/src/util/entities/Webhook.ts b/src/util/entities/Webhook.ts
index 3d94ddb6..42f74fae 100644
--- a/src/util/entities/Webhook.ts
+++ b/src/util/entities/Webhook.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import "reflect-metadata";
+import { Column, Entity, JoinColumn, ManyToOne, Relation, RelationId } from "typeorm";
 import { Application } from "./Application";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
@@ -32,7 +33,7 @@ export class Webhook extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	guild: Guild;
+	guild: Relation<Guild>;
 
 	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.channel)
@@ -42,7 +43,7 @@ export class Webhook extends BaseClass {
 	@ManyToOne(() => Channel, {
 		onDelete: "CASCADE"
 	})
-	channel: Channel;
+	channel: Relation<Channel>;
 
 	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.application)
@@ -52,7 +53,7 @@ export class Webhook extends BaseClass {
 	@ManyToOne(() => Application, {
 		onDelete: "CASCADE"
 	})
-	application: Application;
+	application: Relation<Application>;
 
 	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.user)
@@ -62,7 +63,7 @@ export class Webhook extends BaseClass {
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE"
 	})
-	user: User;
+	user: Relation<User>;
 
 	@Column({ nullable: true })
 	@RelationId((webhook: Webhook) => webhook.guild)
@@ -72,5 +73,5 @@ export class Webhook extends BaseClass {
 	@ManyToOne(() => Guild, {
 		onDelete: "CASCADE"
 	})
-	source_guild: Guild;
+	source_guild: Relation<Guild>;
 }
diff --git a/src/util/migrations/mariadb/1660258393551-CodeCleanup3.ts b/src/util/migrations/mariadb/1660258393551-CodeCleanup3.ts
new file mode 100644
index 00000000..8a6126c7
--- /dev/null
+++ b/src/util/migrations/mariadb/1660258393551-CodeCleanup3.ts
@@ -0,0 +1,231 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class CodeCleanup31660258393551 implements MigrationInterface {
+	name = "CodeCleanup31660258393551";
+
+	public async up(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_2ce5a55796fe4c2f77ece57a647\`
+        `);
+		await queryRunner.query(`
+            DROP INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\`
+        `);
+		await queryRunner.query(`
+            CREATE TABLE \`user_settings\` (
+                \`id\` varchar(255) NOT NULL,
+                \`afk_timeout\` int NULL,
+                \`allow_accessibility_detection\` tinyint NULL,
+                \`animate_emoji\` tinyint NULL,
+                \`animate_stickers\` int NULL,
+                \`contact_sync_enabled\` tinyint NULL,
+                \`convert_emoticons\` tinyint NULL,
+                \`custom_status\` text NULL,
+                \`default_guilds_restricted\` tinyint NULL,
+                \`detect_platform_accounts\` tinyint NULL,
+                \`developer_mode\` tinyint NULL,
+                \`disable_games_tab\` tinyint NULL,
+                \`enable_tts_command\` tinyint NULL,
+                \`explicit_content_filter\` int NULL,
+                \`friend_source_flags\` text NULL,
+                \`gateway_connected\` tinyint NULL,
+                \`gif_auto_play\` tinyint NULL,
+                \`guild_folders\` text NULL,
+                \`guild_positions\` text NULL,
+                \`inline_attachment_media\` tinyint NULL,
+                \`inline_embed_media\` tinyint NULL,
+                \`locale\` varchar(255) NULL,
+                \`message_display_compact\` tinyint NULL,
+                \`native_phone_integration_enabled\` tinyint NULL,
+                \`render_embeds\` tinyint NULL,
+                \`render_reactions\` tinyint NULL,
+                \`restricted_guilds\` text NULL,
+                \`show_current_game\` tinyint NULL,
+                \`status\` varchar(255) NULL,
+                \`stream_notifications_enabled\` tinyint NULL,
+                \`theme\` varchar(255) NULL,
+                \`timezone_offset\` int NULL,
+                PRIMARY KEY (\`id\`)
+            ) ENGINE = InnoDB
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP COLUMN \`settings\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`type\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`hook\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`redirect_uris\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`rpc_application_state\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`store_application_state\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`verification_state\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`interactions_endpoint_url\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`integration_public\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`integration_require_code_grant\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`discoverability_state\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`discovery_eligibility_flags\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`tags\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`install_params\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`bot_user_id\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`guilds\`
+            ADD \`premium_progress_bar_enabled\` tinyint NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`rpc_origins\` text NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`primary_sku_id\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`slug\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`guild_id\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NOT NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`flags\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`flags\` varchar(255) NOT NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD CONSTRAINT \`FK_e5bf78cdbbe9ba91062d74c5aba\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_e5bf78cdbbe9ba91062d74c5aba\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`flags\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`flags\` int NOT NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`guild_id\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`slug\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`primary_sku_id\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`rpc_origins\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`guilds\` DROP COLUMN \`premium_progress_bar_enabled\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`bot_user_id\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`install_params\` text NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`tags\` text NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`discovery_eligibility_flags\` int NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`discoverability_state\` int NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`integration_require_code_grant\` tinyint NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`integration_public\` tinyint NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`interactions_endpoint_url\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`verification_state\` int NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`store_application_state\` int NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`rpc_application_state\` int NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`redirect_uris\` text NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`hook\` tinyint NOT NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`type\` text NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD \`settings\` text NOT NULL
+        `);
+		await queryRunner.query(`
+            DROP TABLE \`user_settings\`
+        `);
+		await queryRunner.query(`
+            CREATE UNIQUE INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` (\`bot_user_id\`)
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD CONSTRAINT \`FK_2ce5a55796fe4c2f77ece57a647\` FOREIGN KEY (\`bot_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+	}
+}
diff --git a/src/util/migrations/mariadb/1660260587556-CodeCleanup4.ts b/src/util/migrations/mariadb/1660260587556-CodeCleanup4.ts
new file mode 100644
index 00000000..aa750f17
--- /dev/null
+++ b/src/util/migrations/mariadb/1660260587556-CodeCleanup4.ts
@@ -0,0 +1,38 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class CodeCleanup41660260587556 implements MigrationInterface {
+	name = "CodeCleanup41660260587556";
+
+	public async up(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD \`settingsId\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD UNIQUE INDEX \`IDX_76ba283779c8441fd5ff819c8c\` (\`settingsId\`)
+        `);
+		await queryRunner.query(`
+            CREATE UNIQUE INDEX \`REL_76ba283779c8441fd5ff819c8c\` ON \`users\` (\`settingsId\`)
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD CONSTRAINT \`FK_76ba283779c8441fd5ff819c8cf\` FOREIGN KEY (\`settingsId\`) REFERENCES \`user_settings\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP FOREIGN KEY \`FK_76ba283779c8441fd5ff819c8cf\`
+        `);
+		await queryRunner.query(`
+            DROP INDEX \`REL_76ba283779c8441fd5ff819c8c\` ON \`users\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP INDEX \`IDX_76ba283779c8441fd5ff819c8c\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP COLUMN \`settingsId\`
+        `);
+	}
+}
diff --git a/src/util/migrations/mariadb/1660265930624-CodeCleanup5.ts b/src/util/migrations/mariadb/1660265930624-CodeCleanup5.ts
new file mode 100644
index 00000000..6629d4d4
--- /dev/null
+++ b/src/util/migrations/mariadb/1660265930624-CodeCleanup5.ts
@@ -0,0 +1,52 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class CodeCleanup51660265930624 implements MigrationInterface {
+	name = "CodeCleanup51660265930624";
+
+	public async up(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD \`settingsId\` varchar(255) NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD UNIQUE INDEX \`IDX_76ba283779c8441fd5ff819c8c\` (\`settingsId\`)
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`channels\`
+            ADD \`flags\` int NULL
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`channels\`
+            ADD \`default_thread_rate_limit_per_user\` int NULL
+        `);
+		await queryRunner.query(`
+            CREATE UNIQUE INDEX \`REL_76ba283779c8441fd5ff819c8c\` ON \`users\` (\`settingsId\`)
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\`
+            ADD CONSTRAINT \`FK_76ba283779c8441fd5ff819c8cf\` FOREIGN KEY (\`settingsId\`) REFERENCES \`user_settings\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP FOREIGN KEY \`FK_76ba283779c8441fd5ff819c8cf\`
+        `);
+		await queryRunner.query(`
+            DROP INDEX \`REL_76ba283779c8441fd5ff819c8c\` ON \`users\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`channels\` DROP COLUMN \`default_thread_rate_limit_per_user\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`channels\` DROP COLUMN \`flags\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP INDEX \`IDX_76ba283779c8441fd5ff819c8c\`
+        `);
+		await queryRunner.query(`
+            ALTER TABLE \`users\` DROP COLUMN \`settingsId\`
+        `);
+	}
+}
diff --git a/src/util/plugin/Plugin.ts b/src/util/plugin/Plugin.ts
new file mode 100644
index 00000000..ee4fd95c
--- /dev/null
+++ b/src/util/plugin/Plugin.ts
@@ -0,0 +1,13 @@
+import { TypedEventEmitter } from "@fosscord/util";
+import EventEmitter from "events";
+
+type PluginEvents = {
+	error: (error: Error | unknown) => void;
+	loaded: () => void;
+};
+
+export class Plugin extends (EventEmitter as new () => TypedEventEmitter<PluginEvents>) {
+	async init() {
+		// insert default config into database?
+	}
+}
diff --git a/src/util/plugin/PluginLoader.ts b/src/util/plugin/PluginLoader.ts
new file mode 100644
index 00000000..000f3345
--- /dev/null
+++ b/src/util/plugin/PluginLoader.ts
@@ -0,0 +1,39 @@
+import fs from "fs";
+import path from "path";
+import { Plugin, PluginManifest } from "./";
+
+const root = process.env.PLUGIN_LOCATION || "../plugins";
+
+let pluginsLoaded = false;
+export class PluginLoader {
+	public static loadPlugins() {
+		console.log(`Plugin root directory: ${path.resolve(root)}`);
+		const dirs = fs.readdirSync(root).filter((x) => {
+			try {
+				fs.readdirSync(path.join(root, x));
+				return true;
+			} catch (e) {
+				return false;
+			}
+		});
+		console.log(dirs);
+		dirs.forEach(async (x) => {
+			let modPath = path.resolve(path.join(root, x));
+			console.log(`Trying to load plugin: ${modPath}`);
+			const manifest = require(path.join(modPath, "plugin.json")) as PluginManifest;
+			console.log(
+				`Plugin info: ${manifest.name} (${manifest.id}), written by ${manifest.authors}, available at ${manifest.repository}`
+			);
+			const module_ = require(path.join(modPath, "dist", "index.js")) as Plugin;
+			try {
+				await module_.init();
+				module_.emit("loaded");
+			} catch (error) {
+				module_.emit("error", error);
+			}
+		});
+
+		//
+		//module_.pluginPath =
+	}
+}
diff --git a/src/util/plugin/PluginManifest.ts b/src/util/plugin/PluginManifest.ts
new file mode 100644
index 00000000..d940f2c8
--- /dev/null
+++ b/src/util/plugin/PluginManifest.ts
@@ -0,0 +1,9 @@
+export class PluginManifest {
+	id: string;
+	name: string;
+	authors: string[];
+	repository: string;
+	license: string;
+	version: string; // semver
+	versionCode: number; // integer
+}
diff --git a/src/util/plugin/index.ts b/src/util/plugin/index.ts
new file mode 100644
index 00000000..c4c0c2ac
--- /dev/null
+++ b/src/util/plugin/index.ts
@@ -0,0 +1,3 @@
+export * from "./Plugin";
+export * from "./PluginLoader";
+export * from "./PluginManifest";
diff --git a/src/util/schemas/SelectProtocolSchema.ts b/src/util/schemas/SelectProtocolSchema.ts
new file mode 100644
index 00000000..0ba0c23b
--- /dev/null
+++ b/src/util/schemas/SelectProtocolSchema.ts
@@ -0,0 +1,19 @@
+export interface SelectProtocolSchema {
+	protocol: "webrtc" | "udp";
+	data:
+		| string
+		| {
+				address: string;
+				port: number;
+				mode: string;
+		  };
+	sdp?: string;
+	codecs?: {
+		name: "opus" | "VP8" | "VP9" | "H264";
+		type: "audio" | "video";
+		priority: number;
+		payload_type: number;
+		rtx_payload_type?: number | null;
+	}[];
+	rtc_connection_id?: string; // uuid
+}
diff --git a/src/util/schemas/Validator.ts b/src/util/schemas/Validator.ts
new file mode 100644
index 00000000..d506e14c
--- /dev/null
+++ b/src/util/schemas/Validator.ts
@@ -0,0 +1,54 @@
+import Ajv from "ajv";
+import addFormats from "ajv-formats";
+import fs from "fs";
+import path from "path";
+
+const SchemaPath = path.join(__dirname, "..", "..", "..", "assets", "schemas.json");
+const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
+
+export const ajv = new Ajv({
+	allErrors: true,
+	parseDate: true,
+	allowDate: true,
+	schemas,
+	coerceTypes: true,
+	messages: true,
+	strict: true,
+	strictRequired: true
+});
+
+addFormats(ajv);
+
+export function validateSchema<G>(schema: string, data: G): G {
+	const valid = ajv.validate(schema, normalizeBody(data));
+	if (!valid) throw ajv.errors;
+	return data;
+}
+
+// Normalizer is introduced to workaround https://github.com/ajv-validator/ajv/issues/1287
+// this removes null values as ajv doesn't treat them as undefined
+// normalizeBody allows to handle circular structures without issues
+// taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license)
+export const normalizeBody = (body: any = {}) => {
+	const normalizedObjectsSet = new WeakSet();
+	const normalizeObject = (object: any) => {
+		if (normalizedObjectsSet.has(object)) return;
+		normalizedObjectsSet.add(object);
+		if (Array.isArray(object)) {
+			for (const [index, value] of object.entries()) {
+				if (typeof value === "object") normalizeObject(value);
+			}
+		} else {
+			for (const [key, value] of Object.entries(object)) {
+				if (value == null) {
+					if (key === "icon" || key === "avatar" || key === "banner" || key === "splash" || key === "discovery_splash") continue;
+					delete object[key];
+				} else if (typeof value === "object") {
+					normalizeObject(value);
+				}
+			}
+		}
+	};
+	normalizeObject(body);
+	return body;
+};
diff --git a/src/util/schemas/VoiceIdentifySchema.ts b/src/util/schemas/VoiceIdentifySchema.ts
new file mode 100644
index 00000000..df023713
--- /dev/null
+++ b/src/util/schemas/VoiceIdentifySchema.ts
@@ -0,0 +1,12 @@
+export interface VoiceIdentifySchema {
+	server_id: string;
+	user_id: string;
+	session_id: string;
+	token: string;
+	video?: boolean;
+	streams?: {
+		type: string;
+		rid: string;
+		quality: number;
+	}[];
+}
diff --git a/src/util/schemas/VoiceVideoSchema.ts b/src/util/schemas/VoiceVideoSchema.ts
new file mode 100644
index 00000000..0ba519e1
--- /dev/null
+++ b/src/util/schemas/VoiceVideoSchema.ts
@@ -0,0 +1,17 @@
+export interface VoiceVideoSchema {
+	audio_ssrc: number;
+	video_ssrc: number;
+	rtx_ssrc?: number;
+	user_id?: string;
+	streams?: {
+		type: "video" | "audio";
+		rid: string;
+		ssrc: number;
+		active: boolean;
+		quality: number;
+		rtx_ssrc: number;
+		max_bitrate: number;
+		max_framerate: number;
+		max_resolution: { type: string; width: number; height: number };
+	}[];
+}
diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index a15ab4b0..9f796cc3 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -30,6 +30,7 @@ export * from "./RelationshipPostSchema";
 export * from "./RelationshipPutSchema";
 export * from "./RoleModifySchema";
 export * from "./RolePositionUpdateSchema";
+export * from "./SelectProtocolSchema";
 export * from "./TemplateCreateSchema";
 export * from "./TemplateModifySchema";
 export * from "./TotpDisableSchema";
@@ -37,7 +38,10 @@ export * from "./TotpEnableSchema";
 export * from "./TotpSchema";
 export * from "./UserModifySchema";
 export * from "./UserSettingsSchema";
+export * from "./Validator";
 export * from "./VanityUrlSchema";
+export * from "./VoiceIdentifySchema";
 export * from "./VoiceStateUpdateSchema";
+export * from "./VoiceVideoSchema";
 export * from "./WebhookCreateSchema";
 export * from "./WidgetModifySchema";
diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts
index 647de26a..247c5715 100644
--- a/src/util/util/Database.ts
+++ b/src/util/util/Database.ts
@@ -91,6 +91,7 @@ function getDataSourceOptions(): DataSourceOptions {
 		cache: {
 			duration: 1000 * 3 // cache all find queries for 3 seconds
 		},
+		// relationLoadStrategy: "query",
 		bigNumberStrings: false,
 		supportBigNumbers: true,
 		name: "default",
diff --git a/src/util/util/NamingStrategy.ts b/src/util/util/NamingStrategy.ts
new file mode 100644
index 00000000..1ff256d6
--- /dev/null
+++ b/src/util/util/NamingStrategy.ts
@@ -0,0 +1,12 @@
+import { DefaultNamingStrategy } from "typeorm";
+
+export class NamingStrategy extends DefaultNamingStrategy {
+	eagerJoinRelationAlias(alias: string, propertyPath: string) {
+		const result = super.eagerJoinRelationAlias(alias, propertyPath);
+
+		console.log({ alias, propertyPath, result });
+		return result;
+	}
+}
+
+export const namingStrategy = new NamingStrategy();
diff --git a/src/util/util/imports/TypedEmitter.ts b/src/util/util/imports/TypedEmitter.ts
new file mode 100644
index 00000000..7a0fffe2
--- /dev/null
+++ b/src/util/util/imports/TypedEmitter.ts
@@ -0,0 +1,41 @@
+export type EventMap = {
+	[key: string]: (...args: any[]) => void;
+};
+
+/**
+ * Type-safe event emitter.
+ *
+ * Use it like this:
+ *
+ * ```typescript
+ * type MyEvents = {
+ *   error: (error: Error) => void;
+ *   message: (from: string, content: string) => void;
+ * }
+ *
+ * const myEmitter = new EventEmitter() as TypedEmitter<MyEvents>;
+ *
+ * myEmitter.emit("error", "x")  // <- Will catch this type error;
+ * ```
+ */
+export interface TypedEventEmitter<Events extends EventMap> {
+	addListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+	on<E extends keyof Events>(event: E, listener: Events[E]): this;
+	once<E extends keyof Events>(event: E, listener: Events[E]): this;
+	prependListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+	prependOnceListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+
+	off<E extends keyof Events>(event: E, listener: Events[E]): this;
+	removeAllListeners<E extends keyof Events>(event?: E): this;
+	removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+
+	emit<E extends keyof Events>(event: E, ...args: Parameters<Events[E]>): boolean;
+	// The sloppy `eventNames()` return type is to mitigate type incompatibilities - see #5
+	eventNames(): (keyof Events | string | symbol)[];
+	rawListeners<E extends keyof Events>(event: E): Events[E][];
+	listeners<E extends keyof Events>(event: E): Events[E][];
+	listenerCount<E extends keyof Events>(event: E): number;
+
+	getMaxListeners(): number;
+	setMaxListeners(maxListeners: number): this;
+}