summary refs log tree commit diff
path: root/util/src
diff options
context:
space:
mode:
Diffstat (limited to 'util/src')
-rw-r--r--util/src/entities/Application.ts6
-rw-r--r--util/src/entities/AuditLog.ts6
-rw-r--r--util/src/entities/Ban.ts8
-rw-r--r--util/src/entities/Channel.ts12
-rw-r--r--util/src/entities/Config.ts274
-rw-r--r--util/src/entities/Emoji.ts4
-rw-r--r--util/src/entities/Guild.ts29
-rw-r--r--util/src/entities/Invite.ts12
-rw-r--r--util/src/entities/Member.ts6
-rw-r--r--util/src/entities/Message.ts29
-rw-r--r--util/src/entities/RateLimit.ts4
-rw-r--r--util/src/entities/ReadState.ts9
-rw-r--r--util/src/entities/Relationship.ts4
-rw-r--r--util/src/entities/Role.ts4
-rw-r--r--util/src/entities/Team.ts6
-rw-r--r--util/src/entities/TeamMember.ts6
-rw-r--r--util/src/entities/Template.ts8
-rw-r--r--util/src/entities/User.ts41
-rw-r--r--util/src/entities/VoiceState.ts11
-rw-r--r--util/src/entities/Webhook.ts46
-rw-r--r--util/src/entities/index.ts1
21 files changed, 444 insertions, 82 deletions
diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts
index 64b5d2e2..90d0f056 100644
--- a/util/src/entities/Application.ts
+++ b/util/src/entities/Application.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Team } from "./Team";
@@ -38,14 +38,14 @@ export class Application extends BaseClass {
 	@Column()
 	verify_key: string;
 
-	@Column()
+	@RelationId((application: Application) => application.team)
 	team_id: string;
 
 	@JoinColumn({ name: "team_id" })
 	@ManyToOne(() => Team, (team: Team) => team.id)
 	team?: Team;
 
-	@Column()
+	@RelationId((application: Application) => application.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
diff --git a/util/src/entities/AuditLog.ts b/util/src/entities/AuditLog.ts
index 53e99261..2195771b 100644
--- a/util/src/entities/AuditLog.ts
+++ b/util/src/entities/AuditLog.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { ChannelPermissionOverwrite } from "./Channel";
 import { User } from "./User";
@@ -43,14 +43,14 @@ export enum AuditLogEvents {
 
 @Entity("audit_logs")
 export class AuditLogEntry extends BaseClass {
-	@Column()
+	@RelationId((auditlog: AuditLogEntry) => auditlog.target)
 	target_id: string;
 
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	target?: User;
 
-	@Column()
+	@RelationId((auditlog: AuditLogEntry) => auditlog.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
diff --git a/util/src/entities/Ban.ts b/util/src/entities/Ban.ts
index ceea4a05..2553e886 100644
--- a/util/src/entities/Ban.ts
+++ b/util/src/entities/Ban.ts
@@ -1,25 +1,25 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { User } from "./User";
 
 @Entity("bans")
 export class Ban extends BaseClass {
-	@Column()
+	@RelationId((ban: Ban) => ban.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	user: User;
 
-	@Column()
+	@RelationId((ban: Ban) => ban.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
 	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
 	guild: Guild;
 
-	@Column()
+	@RelationId((ban: Ban) => ban.executor)
 	executor_id: string;
 
 	@JoinColumn({ name: "executor_id" })
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 5845607a..d26f1054 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Message } from "./Message";
@@ -25,35 +25,35 @@ 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[];
 
-	@Column()
+	@RelationId((channel: Channel) => channel.last_message)
 	last_message_id: string;
 
 	@JoinColumn({ name: "last_message_id" })
 	@ManyToOne(() => Message, (message: Message) => message.id)
 	last_message?: Message;
 
-	@Column()
+	@RelationId((channel: Channel) => channel.guild)
 	guild_id?: string;
 
 	@JoinColumn({ name: "guild_id" })
 	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
 	guild: Guild;
 
-	@Column()
+	@RelationId((channel: Channel) => channel.parent)
 	parent_id: string;
 
 	@JoinColumn({ name: "parent_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	parent?: Channel;
 
-	@Column()
+	@RelationId((channel: Channel) => channel.owner)
 	owner_id: string;
 
 	@JoinColumn({ name: "owner_id" })
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
new file mode 100644
index 00000000..60d84958
--- /dev/null
+++ b/util/src/entities/Config.ts
@@ -0,0 +1,274 @@
+import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Snowflake } from "../util";
+import { BaseClass } from "./BaseClass";
+import crypto from "crypto";
+
+@Entity("config")
+export class ConfigEntity extends BaseClass {
+	@Column("simple-json")
+	value: ConfigValue;
+}
+
+export interface RateLimitOptions {
+	bot?: number;
+	count: number;
+	window: number;
+	onyIp?: boolean;
+}
+
+export interface Region {
+	id: string;
+	name: string;
+	vip: boolean;
+	custom: boolean;
+	deprecated: boolean;
+	optimal: boolean;
+}
+
+export interface KafkaBroker {
+	ip: string;
+	port: number;
+}
+
+export interface ConfigValue {
+	gateway: {
+		endpointClient: string | null;
+		endpoint: string | null;
+	};
+	cdn: {
+		endpointClient: string | null;
+		endpoint: string | null;
+	};
+	general: {
+		instance_id: string;
+	};
+	permissions: {
+		user: {
+			createGuilds: boolean;
+		};
+	};
+	limits: {
+		user: {
+			maxGuilds: number;
+			maxUsername: number;
+			maxFriends: number;
+		};
+		guild: {
+			maxRoles: number;
+			maxMembers: number;
+			maxChannels: number;
+			maxChannelsInCategory: number;
+			hideOfflineMember: number;
+		};
+		message: {
+			maxCharacters: number;
+			maxTTSCharacters: number;
+			maxReactions: number;
+			maxAttachmentSize: number;
+			maxBulkDelete: number;
+		};
+		channel: {
+			maxPins: number;
+			maxTopic: number;
+		};
+		rate: {
+			ip: Omit<RateLimitOptions, "bot_count">;
+			global: RateLimitOptions;
+			error: RateLimitOptions;
+			routes: {
+				guild: RateLimitOptions;
+				webhook: RateLimitOptions;
+				channel: RateLimitOptions;
+				auth: {
+					login: RateLimitOptions;
+					register: RateLimitOptions;
+				};
+				// TODO: rate limit configuration for all routes
+			};
+		};
+	};
+	security: {
+		autoUpdate: boolean | number;
+		requestSignature: string;
+		jwtSecret: string;
+		forwadedFor: string | null; // header to get the real user ip address
+		captcha: {
+			enabled: boolean;
+			service: "recaptcha" | "hcaptcha" | null; // TODO: hcaptcha, custom
+			sitekey: string | null;
+			secret: string | null;
+		};
+		ipdataApiKey: string | null;
+	};
+	login: {
+		requireCaptcha: boolean;
+	};
+	register: {
+		email: {
+			necessary: boolean; // we have to use necessary instead of required as the cli tool uses json schema and can't use required
+			allowlist: boolean;
+			blocklist: boolean;
+			domains: string[];
+		};
+		dateOfBirth: {
+			necessary: boolean;
+			minimum: number; // in years
+		};
+		requireCaptcha: boolean;
+		requireInvite: boolean;
+		allowNewRegistration: boolean;
+		allowMultipleAccounts: boolean;
+		blockProxies: boolean;
+		password: {
+			minLength: number;
+			minNumbers: number;
+			minUpperCase: number;
+			minSymbols: number;
+		};
+	};
+	regions: {
+		default: string;
+		available: Region[];
+	};
+	rabbitmq: {
+		host: string | null;
+	};
+	kafka: {
+		brokers: KafkaBroker[] | null;
+	};
+}
+
+export const DefaultConfigOptions: ConfigValue = {
+	gateway: {
+		endpointClient: null,
+		endpoint: null,
+	},
+	cdn: {
+		endpointClient: null,
+		endpoint: null,
+	},
+	general: {
+		instance_id: Snowflake.generate(),
+	},
+	permissions: {
+		user: {
+			createGuilds: true,
+		},
+	},
+	limits: {
+		user: {
+			maxGuilds: 100,
+			maxUsername: 32,
+			maxFriends: 1000,
+		},
+		guild: {
+			maxRoles: 250,
+			maxMembers: 250000,
+			maxChannels: 500,
+			maxChannelsInCategory: 50,
+			hideOfflineMember: 1000,
+		},
+		message: {
+			maxCharacters: 2000,
+			maxTTSCharacters: 200,
+			maxReactions: 20,
+			maxAttachmentSize: 8388608,
+			maxBulkDelete: 100,
+		},
+		channel: {
+			maxPins: 50,
+			maxTopic: 1024,
+		},
+		rate: {
+			ip: {
+				count: 500,
+				window: 5,
+			},
+			global: {
+				count: 20,
+				window: 5,
+				bot: 250,
+			},
+			error: {
+				count: 10,
+				window: 5,
+			},
+			routes: {
+				guild: {
+					count: 5,
+					window: 5,
+				},
+				webhook: {
+					count: 5,
+					window: 20,
+				},
+				channel: {
+					count: 5,
+					window: 20,
+				},
+				auth: {
+					login: {
+						count: 5,
+						window: 60,
+					},
+					register: {
+						count: 2,
+						window: 60 * 60 * 12,
+					},
+				},
+			},
+		},
+	},
+	security: {
+		autoUpdate: true,
+		requestSignature: crypto.randomBytes(32).toString("base64"),
+		jwtSecret: crypto.randomBytes(256).toString("base64"),
+		forwadedFor: null,
+		// forwadedFor: "X-Forwarded-For" // nginx/reverse proxy
+		// forwadedFor: "CF-Connecting-IP" // cloudflare:
+		captcha: {
+			enabled: false,
+			service: null,
+			sitekey: null,
+			secret: null,
+		},
+		ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9",
+	},
+	login: {
+		requireCaptcha: false,
+	},
+	register: {
+		email: {
+			necessary: true,
+			allowlist: false,
+			blocklist: true,
+			domains: [], // TODO: efficiently save domain blocklist in database
+			// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
+		},
+		dateOfBirth: {
+			necessary: true,
+			minimum: 13,
+		},
+		requireInvite: false,
+		requireCaptcha: true,
+		allowNewRegistration: true,
+		allowMultipleAccounts: true,
+		blockProxies: true,
+		password: {
+			minLength: 8,
+			minNumbers: 2,
+			minUpperCase: 2,
+			minSymbols: 0,
+		},
+	},
+	regions: {
+		default: "fosscord",
+		available: [{ id: "fosscord", name: "Fosscord", vip: false, custom: false, deprecated: false, optimal: false }],
+	},
+	rabbitmq: {
+		host: null,
+	},
+	kafka: {
+		brokers: null,
+	},
+};
diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts
index 366549db..b31ddb3b 100644
--- a/util/src/entities/Emoji.ts
+++ b/util/src/entities/Emoji.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Role } from "./Role";
@@ -30,7 +30,7 @@ export class Emoji extends BaseClass {
 	@Column()
 	url: string;
 
-	@Column("simple-array")
+	@RelationId((emoji: Emoji) => emoji.roles)
 	role_ids: string[];
 
 	@JoinColumn({ name: "role_ids" })
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index d46d31bc..d7b4dff4 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Emoji } from "./Emoji";
@@ -10,7 +10,7 @@ import { VoiceState } from "./VoiceState";
 
 @Entity("guilds")
 export class Guild extends BaseClass {
-	@Column()
+	@RelationId((guild: Guild) => guild.afk_channel)
 	afk_channel_id?: string;
 
 	@JoinColumn({ name: "afk_channel_id" })
@@ -64,35 +64,35 @@ export class Guild extends BaseClass {
 	@Column()
 	presence_count?: number; // users online
 
-	@Column("simple-array")
+	@RelationId((guild: Guild) => guild.members)
 	member_ids: string[];
 
 	@JoinColumn({ name: "member_ids" })
 	@ManyToMany(() => Member, (member: Member) => member.id)
 	members: Member[];
 
-	@Column("simple-array")
+	@RelationId((guild: Guild) => guild.roles)
 	role_ids: string[];
 
 	@JoinColumn({ name: "role_ids" })
 	@ManyToMany(() => Role, (role: Role) => role.id)
 	roles: Role[];
 
-	@Column("simple-array")
+	@RelationId((guild: Guild) => guild.channels)
 	channel_ids: string[];
 
 	@JoinColumn({ name: "channel_ids" })
 	@ManyToMany(() => Channel, (channel: Channel) => channel.id)
 	channels: Channel[];
 
-	@Column("simple-array")
+	@RelationId((guild: Guild) => guild.emojis)
 	emoji_ids: string[];
 
 	@JoinColumn({ name: "emoji_ids" })
 	@ManyToMany(() => Emoji, (emoji: Emoji) => emoji.id)
 	emojis: Emoji[];
 
-	@Column("simple-array")
+	@RelationId((guild: Guild) => guild.voice_states)
 	voice_state_ids: string[];
 
 	@JoinColumn({ name: "voice_state_ids" })
@@ -105,7 +105,7 @@ export class Guild extends BaseClass {
 	@Column()
 	name: string;
 
-	@Column()
+	@RelationId((guild: Guild) => guild.owner)
 	owner_id: string;
 
 	@JoinColumn({ name: "owner_id" })
@@ -121,11 +121,14 @@ export class Guild extends BaseClass {
 	@Column()
 	premium_tier?: number; // nitro boost level
 
+	@RelationId((guild: Guild) => guild.public_updates_channel)
+	public_updates_channel_id: string;
+
 	@JoinColumn({ name: "public_updates_channel_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	public_updates_channel?: Channel;
 
-	@Column()
+	@RelationId((guild: Guild) => guild.rules_channel)
 	rules_channel_id?: string;
 
 	@JoinColumn({ name: "rules_channel_id" })
@@ -138,7 +141,7 @@ export class Guild extends BaseClass {
 	@Column()
 	splash?: string;
 
-	@Column()
+	@RelationId((guild: Guild) => guild.system_channel)
 	system_channel_id?: string;
 
 	@JoinColumn({ name: "system_channel_id" })
@@ -151,6 +154,9 @@ export class Guild extends BaseClass {
 	@Column()
 	unavailable?: boolean;
 
+	@RelationId((guild: Guild) => guild.vanity_url)
+	vanity_url_code?: string;
+
 	@JoinColumn({ name: "vanity_url_code" })
 	@OneToOne(() => Invite, (invite: Invite) => invite.code)
 	vanity_url?: Invite;
@@ -170,6 +176,9 @@ export class Guild extends BaseClass {
 		}[];
 	};
 
+	@RelationId((guild: Guild) => guild.widget_channel)
+	widget_channel_id?: string;
+
 	@JoinColumn({ name: "widget_channel_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	widget_channel?: Channel;
diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts
index 19f7206a..eac0cba0 100644
--- a/util/src/entities/Invite.ts
+++ b/util/src/entities/Invite.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Guild } from "./Guild";
@@ -27,29 +27,29 @@ export class Invite extends BaseClass {
 	@Column()
 	expires_at: Date;
 
-	@Column()
+	@RelationId((invite: Invite) => invite.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
 	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
 	guild: Guild;
 
-	@Column()
+	@RelationId((invite: Invite) => invite.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	channel: Channel;
 
-	@Column()
+	@RelationId((invite: Invite) => invite.inviter)
 	inviter_id: string;
 
 	@JoinColumn({ name: "inviter_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	inviter: User;
 
-	@Column()
-	target_usser_id: string;
+	@RelationId((invite: Invite) => invite.target_user)
+	target_user_id: string;
 
 	@JoinColumn({ name: "target_user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index c367755e..01634d9e 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -1,18 +1,18 @@
 import { PublicUser, User } from "./User";
 import { BaseClass } from "./BaseClass";
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { Guild } from "./Guild";
 
 @Entity("members")
 export class Member extends BaseClass {
-	@Column()
+	@RelationId((member: Member) => member.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	user: User;
 
-	@Column()
+	@RelationId((member: Member) => member.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 2c0918c7..9daf042c 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -4,7 +4,16 @@ import { Role } from "./Role";
 import { Channel } from "./Channel";
 import { InteractionType } from "../interfaces/Interaction";
 import { Application } from "./Application";
-import { Column, CreateDateColumn, Entity, JoinColumn, ManyToMany, ManyToOne, UpdateDateColumn } from "typeorm";
+import {
+	Column,
+	CreateDateColumn,
+	Entity,
+	JoinColumn,
+	ManyToMany,
+	ManyToOne,
+	RelationId,
+	UpdateDateColumn,
+} from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { Webhook } from "./Webhook";
@@ -34,42 +43,42 @@ export class Message extends BaseClass {
 	@Column()
 	id: string;
 
-	@Column()
+	@RelationId((message: Message) => message.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	channel: Channel;
 
-	@Column()
+	@RelationId((message: Message) => message.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
 	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
 	guild?: Guild;
 
-	@Column()
+	@RelationId((message: Message) => message.author)
 	author_id: string;
 
 	@JoinColumn({ name: "author_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	author?: User;
 
-	@Column()
+	@RelationId((message: Message) => message.member)
 	member_id: string;
 
 	@JoinColumn({ name: "member_id" })
 	@ManyToOne(() => Member, (member: Member) => member.id)
 	member?: Member;
 
-	@Column()
+	@RelationId((message: Message) => message.webhook)
 	webhook_id: string;
 
 	@JoinColumn({ name: "webhook_id" })
 	@ManyToOne(() => Webhook, (webhook: Webhook) => webhook.id)
 	webhook?: Webhook;
 
-	@Column()
+	@RelationId((message: Message) => message.application)
 	application_id: string;
 
 	@JoinColumn({ name: "application_id" })
@@ -93,21 +102,21 @@ export class Message extends BaseClass {
 	@Column()
 	mention_everyone?: boolean;
 
-	@Column("simple-array")
+	@RelationId((message: Message) => message.mention_users)
 	mention_user_ids: string[];
 
 	@JoinColumn({ name: "mention_user_ids" })
 	@ManyToMany(() => User, (user: User) => user.id)
 	mention_users: User[];
 
-	@Column("simple-array")
+	@RelationId((message: Message) => message.mention_roles)
 	mention_role_ids: string[];
 
 	@JoinColumn({ name: "mention_role_ids" })
 	@ManyToMany(() => Role, (role: Role) => role.id)
 	mention_roles: Role[];
 
-	@Column("simple-array")
+	@RelationId((message: Message) => message.mention_channels)
 	mention_channel_ids: string[];
 
 	@JoinColumn({ name: "mention_channel_ids" })
diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts
index 374a0759..3ac35df3 100644
--- a/util/src/entities/RateLimit.ts
+++ b/util/src/entities/RateLimit.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
@@ -7,7 +7,7 @@ export class RateLimit extends BaseClass {
 	@Column()
 	id: "global" | "error" | string; // channel_239842397 | guild_238927349823 | webhook_238923423498
 
-	@Column()
+	@RelationId((rate_limit: RateLimit) => rate_limit.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index 7c56b6c6..0310cb5f 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Message } from "./Message";
@@ -6,20 +6,23 @@ import { User } from "./User";
 
 @Entity("read_states")
 export class ReadState extends BaseClass {
-	@Column()
+	@RelationId((read_state: ReadState) => read_state.channel)
 	channel_id: string;
 
 	@JoinColumn({ name: "channel_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	channel: Channel;
 
-	@Column()
+	@RelationId((read_state: ReadState) => read_state.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	user: User;
 
+	@RelationId((read_state: ReadState) => read_state.last_message)
+	last_message_id: string;
+
 	@JoinColumn({ name: "last_message_id" })
 	@ManyToOne(() => Message, (message: Message) => message.id)
 	last_message?: Message;
diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts
index bd5861f0..3e1280c7 100644
--- a/util/src/entities/Relationship.ts
+++ b/util/src/entities/Relationship.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
@@ -11,7 +11,7 @@ export enum RelationshipType {
 
 @Entity("relationships")
 export class Relationship extends BaseClass {
-	@Column()
+	@RelationId((relationship: Relationship) => relationship.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts
index 7bb144cc..e48fd293 100644
--- a/util/src/entities/Role.ts
+++ b/util/src/entities/Role.ts
@@ -1,10 +1,10 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 
 @Entity("roles")
 export class Role extends BaseClass {
-	@Column()
+	@RelationId((role: Role) => role.guild)
 	guild_id: string;
 
 	@JoinColumn({ name: "guild_id" })
diff --git a/util/src/entities/Team.ts b/util/src/entities/Team.ts
index 5e645650..fa1b0ed2 100644
--- a/util/src/entities/Team.ts
+++ b/util/src/entities/Team.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { TeamMember } from "./TeamMember";
 import { User } from "./User";
@@ -8,7 +8,7 @@ export class Team extends BaseClass {
 	@Column()
 	icon?: string;
 
-	@Column("simple-array")
+	@RelationId((team: Team) => team.members)
 	member_ids: string[];
 
 	@JoinColumn({ name: "member_ids" })
@@ -18,7 +18,7 @@ export class Team extends BaseClass {
 	@Column()
 	name: string;
 
-	@Column()
+	@RelationId((team: Team) => team.owner_user)
 	owner_user_id: string;
 
 	@JoinColumn({ name: "owner_user_id" })
diff --git a/util/src/entities/TeamMember.ts b/util/src/entities/TeamMember.ts
index 2b1c76f1..f0b54c6f 100644
--- a/util/src/entities/TeamMember.ts
+++ b/util/src/entities/TeamMember.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
@@ -15,14 +15,14 @@ export class TeamMember extends BaseClass {
 	@Column("simple-array")
 	permissions: string[];
 
-	@Column()
+	@RelationId((member: TeamMember) => member.team)
 	team_id: string;
 
 	@JoinColumn({ name: "team_id" })
 	@ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.id)
 	team: import("./Team").Team;
 
-	@Column()
+	@RelationId((member: TeamMember) => member.user)
 	user_id: string;
 
 	@JoinColumn({ name: "user_id" })
diff --git a/util/src/entities/Template.ts b/util/src/entities/Template.ts
index 5c9a5120..c8d2034c 100644
--- a/util/src/entities/Template.ts
+++ b/util/src/entities/Template.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
 import { User } from "./User";
@@ -17,6 +17,9 @@ export class Template extends BaseClass {
 	@Column()
 	usage_count?: number;
 
+	@RelationId((template: Template) => template.creator)
+	creator_id: string;
+
 	@JoinColumn({ name: "creator_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	creator: User;
@@ -27,6 +30,9 @@ export class Template extends BaseClass {
 	@Column()
 	updated_at: Date;
 
+	@RelationId((template: Template) => template.source_guild)
+	source_guild_id: string;
+
 	@JoinColumn({ name: "source_guild_id" })
 	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
 	source_guild: Guild;
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 39e59da4..bdf0d35f 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -1,8 +1,10 @@
-import { Column, Entity, JoinColumn, OneToMany } from "typeorm";
+import { Column, Entity, JoinColumn, 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 { Guild } from "./Guild";
 
 export const PublicUserProjection = {
 	username: true,
@@ -24,6 +26,13 @@ export class User extends BaseClass {
 	@Column()
 	discriminator: string; // #0001 4 digit long string from #0001 - #9999
 
+	setDiscriminator(val: string) {
+		const number = Number(val);
+		if (isNaN(number)) throw new Error("invalid discriminator");
+		if (number > 0 && number < 10000) throw new Error("discriminator must be between 1 and 9999");
+		this.discriminator = val;
+	}
+
 	@Column()
 	avatar?: string; // hash of the user avatar
 
@@ -84,17 +93,21 @@ export class User extends BaseClass {
 	@Column({ type: "bigint" })
 	public_flags: bigint;
 
-	@Column("simple-array") // string in simple-array must not contain commas
-	guilds: string[]; // array of guild ids the user is part of
+	@RelationId((user: User) => user.guilds)
+	guild_ids: string[]; // array of guild ids the user is part of
+
+	@JoinColumn({ name: "guild_ids" })
+	@OneToMany(() => Guild, (guild: Guild) => guild.id)
+	guilds: Guild[];
 
-	@Column("simple-array") // string in simple-array must not contain commas
+	@RelationId((user: User) => user.relationships)
 	relationship_ids: string[]; // array of guild ids the user is part of
 
 	@JoinColumn({ name: "relationship_ids" })
 	@OneToMany(() => User, (user: User) => user.id)
 	relationships: Relationship[];
 
-	@Column("simple-array") // string in simple-array must not contain commas
+	@RelationId((user: User) => user.connected_accounts)
 	connected_account_ids: string[]; // array of guild ids the user is part of
 
 	@JoinColumn({ name: "connected_account_ids" })
@@ -102,14 +115,28 @@ export class User extends BaseClass {
 	connected_accounts: ConnectedAccount[];
 
 	@Column({ type: "simple-json", select: false })
-	user_data: {
+	data: {
 		valid_tokens_since: Date; // all tokens with a previous issue date are invalid
 		hash: string; // hash of the password, salt is saved in password (bcrypt)
-		fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts
 	};
 
+	@Column({ type: "simple-array" })
+	fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts
+
 	@Column("simple-json")
 	settings: UserSettings;
+
+	static async getPublicUser(user_id: string, additional_fields?: any) {
+		const user = await User.findOne(
+			{ id: user_id },
+			{
+				...PublicUserProjection,
+				...additional_fields,
+			}
+		);
+		if (!user) throw new HTTPError("User not found", 404);
+		return user;
+	}
 }
 
 export interface UserSettings {
diff --git a/util/src/entities/VoiceState.ts b/util/src/entities/VoiceState.ts
index 2416c6c0..6707a575 100644
--- a/util/src/entities/VoiceState.ts
+++ b/util/src/entities/VoiceState.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, OneToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
 import { Guild } from "./Guild";
@@ -6,14 +6,23 @@ import { User } from "./User";
 
 @Entity("voice_states")
 export class VoiceState extends BaseClass {
+	@RelationId((voice_state: VoiceState) => voice_state.guild)
+	guild_id: string;
+
 	@JoinColumn({ name: "guild_id" })
 	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
 	guild?: Guild;
 
+	@RelationId((voice_state: VoiceState) => voice_state.channel)
+	channel_id: string;
+
 	@JoinColumn({ name: "channel_id" })
 	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
 	channel: Channel;
 
+	@RelationId((voice_state: VoiceState) => voice_state.user)
+	user_id: string;
+
 	@JoinColumn({ name: "user_id" })
 	@ManyToOne(() => User, (user: User) => user.id)
 	user: User;
diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts
index 54233638..dc929c18 100644
--- a/util/src/entities/Webhook.ts
+++ b/util/src/entities/Webhook.ts
@@ -1,5 +1,9 @@
-import { Column, Entity, JoinColumn } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { Application } from "./Application";
 import { BaseClass } from "./BaseClass";
+import { Channel } from "./Channel";
+import { Guild } from "./Guild";
+import { User } from "./User";
 
 export enum WebhookType {
 	Incoming = 1,
@@ -23,18 +27,38 @@ export class Webhook extends BaseClass {
 	@Column()
 	token?: string;
 
-	@JoinColumn()
-	guild?: string;
+	@RelationId((webhook: Webhook) => webhook.guild)
+	guild_id: string;
 
-	@JoinColumn()
-	channel: string;
+	@JoinColumn({ name: "guild_id" })
+	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	guild: Guild;
 
-	@JoinColumn()
-	application?: string;
+	@RelationId((webhook: Webhook) => webhook.channel)
+	channel_id: string;
 
-	@JoinColumn()
-	user?: string;
+	@JoinColumn({ name: "channel_id" })
+	@ManyToOne(() => Channel, (channel: Channel) => channel.id)
+	channel: Channel;
 
-	@JoinColumn()
-	source_guild: string;
+	@RelationId((webhook: Webhook) => webhook.application)
+	application_id: string;
+
+	@JoinColumn({ name: "application_id" })
+	@ManyToOne(() => Application, (application: Application) => application.id)
+	application: Application;
+
+	@RelationId((webhook: Webhook) => webhook.user)
+	user_id: string;
+
+	@JoinColumn({ name: "user_id" })
+	@ManyToOne(() => User, (user: User) => user.id)
+	user: User;
+
+	@RelationId((webhook: Webhook) => webhook.guild)
+	source_guild_id: string;
+
+	@JoinColumn({ name: "source_guild_id" })
+	@ManyToOne(() => Guild, (guild: Guild) => guild.id)
+	source_guild: Guild;
 }
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index 9cb10016..b9e361c1 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -3,6 +3,7 @@ export * from "./AuditLog";
 export * from "./Ban";
 export * from "./BaseClass";
 export * from "./Channel";
+export * from "./Config";
 export * from "./ConnectedAccount";
 export * from "./Emoji";
 export * from "./Guild";