summary refs log tree commit diff
diff options
context:
space:
mode:
authorTheArcaneBrony <myrainbowdash949@gmail.com>2022-08-08 22:49:16 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2022-08-09 23:28:27 +0200
commit26293f37a1f7af131507c0bdd490d2e07f1a9ba7 (patch)
tree3fdaa7b65f1b66069d08d06143a43c9908395a09
parentClean up BaseClient assign call (diff)
downloadserver-26293f37a1f7af131507c0bdd490d2e07f1a9ba7.tar.xz
Use deep merge everywhere
-rw-r--r--api/src/routes/channels/#channel_id/index.ts3
-rw-r--r--api/src/routes/channels/#channel_id/invites.ts3
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/ack.ts3
-rw-r--r--api/src/routes/channels/#channel_id/recipients.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/bans.ts5
-rw-r--r--api/src/routes/guilds/#guild_id/emojis.ts5
-rw-r--r--api/src/routes/guilds/#guild_id/index.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/members/#member_id/index.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/roles/#role_id/index.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/roles/index.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/stickers.ts5
-rw-r--r--api/src/routes/guilds/#guild_id/templates.ts7
-rw-r--r--api/src/routes/guilds/#guild_id/vanity-url.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts3
-rw-r--r--api/src/routes/guilds/#guild_id/widget.json.ts3
-rw-r--r--api/src/routes/guilds/templates/index.ts5
-rw-r--r--api/src/routes/users/@me/index.ts6
-rw-r--r--api/src/routes/users/@me/relationships.ts7
-rw-r--r--api/src/util/handlers/Message.ts9
-rw-r--r--gateway/src/opcodes/Identify.ts5
-rw-r--r--gateway/src/opcodes/VoiceStateUpdate.ts5
-rw-r--r--util/src/entities/Channel.ts11
-rw-r--r--util/src/entities/Guild.ts7
-rw-r--r--util/src/entities/Member.ts9
-rw-r--r--util/src/entities/User.ts5
-rw-r--r--util/src/util/imports/OrmUtils.ts113
-rw-r--r--util/src/util/imports/index.ts1
27 files changed, 189 insertions, 49 deletions
diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts
index 4001b834..68b52be6 100644
--- a/api/src/routes/channels/#channel_id/index.ts
+++ b/api/src/routes/channels/#channel_id/index.ts
@@ -10,6 +10,7 @@ import {
 } from "@fosscord/util";
 import { Request, Response, Router } from "express";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 // TODO: delete channel
@@ -78,7 +79,7 @@ router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANN
 	if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
 
 	let channel = await Channel.findOneOrFail({ where: { id: channel_id } });
-	channel = Object.assign(channel, payload);
+	channel = OrmUtils.mergeDeep(channel, payload);
 
 	await Promise.all([
 		channel.save(),
diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts
index 4e97b275..a53b1de4 100644
--- a/api/src/routes/channels/#channel_id/invites.ts
+++ b/api/src/routes/channels/#channel_id/invites.ts
@@ -4,6 +4,7 @@ import { route } from "@fosscord/api";
 import { random } from "@fosscord/api";
 import { Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util";
 import { isTextChannel } from "./messages";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 
@@ -33,7 +34,7 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
 
 	const expires_at = new Date(req.body.max_age * 1000 + Date.now());
 
-	const invite = await Object.assign(new Invite(),{
+	const invite = await OrmUtils.mergeDeep(new Invite(),{
 		code: random(),
 		temporary: req.body.temporary,
 		uses: 0,
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
index fc2ec488..c1b8d20f 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -1,6 +1,7 @@
 import { emitEvent, getPermission, MessageAckEvent, ReadState, Snowflake } from "@fosscord/util";
 import { Request, Response, Router } from "express";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -20,7 +21,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques
 	permission.hasThrow("VIEW_CHANNEL");
 
 	let read_state = await ReadState.findOne({ where: { user_id: req.user_id, channel_id } });
-	if (!read_state) read_state = Object.assign(new ReadState(), { user_id: req.user_id, channel_id });
+	if (!read_state) read_state = OrmUtils.mergeDeep(new ReadState(), { user_id: req.user_id, channel_id }) as ReadState;
 	read_state.last_message_id = message_id;
 
 	await read_state.save();
diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts
index d6e25599..069212e2 100644
--- a/api/src/routes/channels/#channel_id/recipients.ts
+++ b/api/src/routes/channels/#channel_id/recipients.ts
@@ -11,6 +11,7 @@ import {
 	User
 } from "@fosscord/util";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 
@@ -28,7 +29,7 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
 			throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
 		}
 
-		channel.recipients!.push(Object.assign(new Recipient(), { channel_id, user_id: user_id }));
+		channel.recipients!.push(OrmUtils.mergeDeep(new Recipient(), { channel_id, user_id: user_id }));
 		await channel.save();
 
 		await emitEvent({
diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts
index 5ff63958..0743687d 100644
--- a/api/src/routes/guilds/#guild_id/bans.ts
+++ b/api/src/routes/guilds/#guild_id/bans.ts
@@ -2,6 +2,7 @@ import { Request, Response, Router } from "express";
 import { DiscordApiErrors, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util";
 import { HTTPError } from "@fosscord/util";
 import { getIpAdress, route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 export interface BanCreateSchema {
 	delete_message_days?: string;
@@ -90,7 +91,7 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER
 	
 	const banned_user = await User.getPublicUser(banned_user_id);
 
-	const ban = Object.assign(new Ban(),{
+	const ban = OrmUtils.mergeDeep(new Ban(),{
 		user_id: banned_user_id,
 		guild_id: guild_id,
 		ip: getIpAdress(req),
@@ -122,7 +123,7 @@ router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res:
 	if (req.permission!.cache.guild?.owner_id === req.params.user_id) 
 		throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
 	
-	const ban = Object.assign(new Ban(), {
+	const ban = OrmUtils.mergeDeep(new Ban(), {
 		user_id: req.params.user_id,
 		guild_id: guild_id,
 		ip: getIpAdress(req),
diff --git a/api/src/routes/guilds/#guild_id/emojis.ts b/api/src/routes/guilds/#guild_id/emojis.ts
index 3e2ed4c2..53a44ec3 100644
--- a/api/src/routes/guilds/#guild_id/emojis.ts
+++ b/api/src/routes/guilds/#guild_id/emojis.ts
@@ -1,6 +1,7 @@
 import { Router, Request, Response } from "express";
 import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User } from "@fosscord/util";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -50,7 +51,7 @@ router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_A
 	const user = await User.findOneOrFail({ where: { id: req.user_id } });
 	body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
 
-	const emoji = await Object.assign(new Emoji(), {
+	const emoji = await OrmUtils.mergeDeep(new Emoji(), {
 		id: id,
 		guild_id: guild_id,
 		...body,
@@ -80,7 +81,7 @@ router.patch(
 		const { emoji_id, guild_id } = req.params;
 		const body = req.body as EmojiModifySchema;
 
-		const emoji = await Object.assign(new Emoji(), { ...body, id: emoji_id, guild_id: guild_id }).save();
+		const emoji = await OrmUtils.mergeDeep(new Emoji(), { ...body, id: emoji_id, guild_id: guild_id }).save();
 
 		await emitEvent({
 			event: "GUILD_EMOJIS_UPDATE",
diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts
index 643db7ce..abece88e 100644
--- a/api/src/routes/guilds/#guild_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/index.ts
@@ -3,6 +3,7 @@ import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpda
 import { HTTPError } from "@fosscord/util";
 import { route } from "@fosscord/api";
 import { GuildCreateSchema } from "../index";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -59,7 +60,7 @@ router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res:
 		relations: ["emojis", "roles", "stickers"]
 	});
 	// TODO: check if body ids are valid
-	guild = Object.assign(guild, body);
+	guild = OrmUtils.mergeDeep(guild, body);
 
 	//TODO: check this, removed toJSON call
 	const data = JSON.parse(JSON.stringify(guild));
diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
index eb31ec1d..824e34a4 100644
--- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -2,6 +2,7 @@ import { Request, Response, Router } from "express";
 import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Rights, Guild } from "@fosscord/util";
 import { HTTPError } from "@fosscord/util";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -31,7 +32,7 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re
 		permission.hasThrow("MANAGE_ROLES");
 
 		if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
-		member.roles = body.roles.map((x) => Object.assign(new Role(), { id: x })); // foreign key constraint will fail if role doesn't exist
+		member.roles = body.roles.map((x) => OrmUtils.mergeDeep(new Role(), { id: x })); // foreign key constraint will fail if role doesn't exist
 	}
 
 	await member.save();
diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts
index a4b91237..0482b9ca 100644
--- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -3,6 +3,7 @@ import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, ha
 import { route } from "@fosscord/api";
 import { HTTPError } from "@fosscord/util";
 import { RoleModifySchema } from "../";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -43,7 +44,7 @@ router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }
 
 	if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
 
-	const role = Object.assign(new Role(), {
+	const role = OrmUtils.mergeDeep(new Role(), {
 		...body,
 		id: role_id,
 		guild_id,
diff --git a/api/src/routes/guilds/#guild_id/roles/index.ts b/api/src/routes/guilds/#guild_id/roles/index.ts
index 7e588d52..6fbb8702 100644
--- a/api/src/routes/guilds/#guild_id/roles/index.ts
+++ b/api/src/routes/guilds/#guild_id/roles/index.ts
@@ -13,6 +13,7 @@ import {
 } from "@fosscord/util";
 import { HTTPError } from "@fosscord/util";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 
@@ -51,7 +52,7 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" })
 
 	if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
 
-	let role: Role = Object.assign(new Role(),{
+	let role: Role = OrmUtils.mergeDeep(new Role(),{
 		// values before ...body are default and can be overriden
 		position: 0,
 		hoist: false,
diff --git a/api/src/routes/guilds/#guild_id/stickers.ts b/api/src/routes/guilds/#guild_id/stickers.ts
index c6a5037f..157ccff4 100644
--- a/api/src/routes/guilds/#guild_id/stickers.ts
+++ b/api/src/routes/guilds/#guild_id/stickers.ts
@@ -13,6 +13,7 @@ import { Router, Request, Response } from "express";
 import { route } from "@fosscord/api";
 import multer from "multer";
 import { HTTPError } from "@fosscord/util";
+import { OrmUtils } from "@fosscord/util";
 const router = Router();
 
 router.get("/", route({}), async (req: Request, res: Response) => {
@@ -43,7 +44,7 @@ router.post(
 		const id = Snowflake.generate();
 
 		const [sticker] = await Promise.all([
-			Object.assign(new Sticker(), {
+			OrmUtils.mergeDeep(new Sticker(), {
 				...body,
 				guild_id,
 				id,
@@ -105,7 +106,7 @@ router.patch(
 		const { guild_id, sticker_id } = req.params;
 		const body = req.body as ModifyGuildStickerSchema;
 
-		const sticker = await Object.assign(new Sticker(), { ...body, guild_id, id: sticker_id }).save();
+		const sticker = await OrmUtils.mergeDeep(new Sticker(), { ...body, guild_id, id: sticker_id }).save();
 		await sendStickerUpdateEvent(guild_id);
 
 		return res.json(sticker);
diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts
index edff6717..3d14de41 100644
--- a/api/src/routes/guilds/#guild_id/templates.ts
+++ b/api/src/routes/guilds/#guild_id/templates.ts
@@ -3,6 +3,7 @@ import { Guild, Template } from "@fosscord/util";
 import { HTTPError } from "@fosscord/util";
 import { route } from "@fosscord/api";
 import { generateCode } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 
@@ -47,7 +48,7 @@ router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD
 	const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => {});
 	if (exists) throw new HTTPError("Template already exists", 400);
 
-	const template = await Object.assign(new Template(), {
+	const template = await OrmUtils.mergeDeep(new Template(), {
 		...req.body,
 		code: generateCode(),
 		creator_id: req.user_id,
@@ -75,7 +76,7 @@ router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request,
 	const { code, guild_id } = req.params;
 	const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
 
-	const template = await Object.assign(new Template(), { code, serialized_source_guild: guild }).save();
+	const template = await OrmUtils.mergeDeep(new Template(), { code, serialized_source_guild: guild }).save();
 
 	res.json(template);
 });
@@ -84,7 +85,7 @@ router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE
 	const { code, guild_id } = req.params;
 	const { name, description } = req.body;
 
-	const template = await Object.assign(new Template(), { code, name: name, description: description, source_guild_id: guild_id }).save();
+	const template = await OrmUtils.mergeDeep(new Template(), { code, name: name, description: description, source_guild_id: guild_id }).save();
 
 	res.json(template);
 });
diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts
index 426559bd..ac46e18a 100644
--- a/api/src/routes/guilds/#guild_id/vanity-url.ts
+++ b/api/src/routes/guilds/#guild_id/vanity-url.ts
@@ -2,6 +2,7 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from
 import { Router, Request, Response } from "express";
 import { route } from "@fosscord/api";
 import { HTTPError } from "@fosscord/util";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -47,7 +48,7 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" })
 
 	const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } });
 
-	await Object.assign(new Invite(), {
+	await OrmUtils.mergeDeep(new Invite(), {
 		vanity_url: true,
 		code: code,
 		temporary: false,
diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 5900963e..32d1aadf 100644
--- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -1,6 +1,7 @@
 import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
 import { route } from "@fosscord/api";
 import { Request, Response, Router } from "express";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 //TODO need more testing when community guild and voice stage channel are working
@@ -42,7 +43,7 @@ router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request
 	});
 	if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
 
-	voice_state = Object.assign(voice_state, body);
+	voice_state = OrmUtils.mergeDeep(voice_state, body) as VoiceState;
 	const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } });
 	if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
 		throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts
index b7a93a46..37739418 100644
--- a/api/src/routes/guilds/#guild_id/widget.json.ts
+++ b/api/src/routes/guilds/#guild_id/widget.json.ts
@@ -2,6 +2,7 @@ import { Request, Response, Router } from "express";
 import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
 import { HTTPError } from "@fosscord/util";
 import { random, route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 
@@ -41,7 +42,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
 			inviter_id: null
 		};
 
-		invite = await new Invite(body).save();
+		invite = await OrmUtils.mergeDeep(new Invite(), body).save();
 	}
 
 	// Fetch voice channels, and the @everyone permissions object
diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts
index bb8cc017..bac4eb8a 100644
--- a/api/src/routes/guilds/templates/index.ts
+++ b/api/src/routes/guilds/templates/index.ts
@@ -4,6 +4,7 @@ import { route } from "@fosscord/api";
 import { DiscordApiErrors } from "@fosscord/util";
 import fetch from "node-fetch";
 const router: Router = Router();
+import { OrmUtils } from "@fosscord/util";
 
 export interface GuildTemplateCreateSchema {
 	name: string;
@@ -57,13 +58,13 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req:
 	const guild_id = Snowflake.generate();
 
 	const [guild, role] = await Promise.all([
-		Object.assign(new Guild(), {
+		OrmUtils.mergeDeep(new Guild(), {
 			...body,
 			...template.serialized_source_guild,
 			id: guild_id,
 			owner_id: req.user_id
 		}).save(),
-		(Object.assign(new Role(), {
+		(OrmUtils.mergeDeep(new Role(), {
 			id: guild_id,
 			guild_id: guild_id,
 			color: 0,
diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts
index 6a2456d6..8ab30a8d 100644
--- a/api/src/routes/users/@me/index.ts
+++ b/api/src/routes/users/@me/index.ts
@@ -2,6 +2,7 @@ import { Router, Request, Response } from "express";
 import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors } from "@fosscord/util";
 import { route } from "@fosscord/api";
 import bcrypt from "bcrypt";
+import { OrmUtils } from "@fosscord/util";
 
 const router: Router = Router();
 
@@ -32,7 +33,8 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
 	const body = req.body as UserModifySchema;
 
 	if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
-	if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
+	if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
+	let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
 
 	if (body.password) {
 		if (user.data?.hash) {
@@ -63,7 +65,7 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
         }
     }
 
-	user = Object.assign(user, body);
+	user = OrmUtils.mergeDeep(user, body);
 	await user.save();
 
 	// @ts-ignore
diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts
index 014a5bd2..6e22f4f3 100644
--- a/api/src/routes/users/@me/relationships.ts
+++ b/api/src/routes/users/@me/relationships.ts
@@ -12,6 +12,7 @@ import { Router, Response, Request } from "express";
 import { HTTPError } from "@fosscord/util";
 import { DiscordApiErrors } from "@fosscord/util";
 import { route } from "@fosscord/api";
+import { OrmUtils } from "@fosscord/util";
 
 const router = Router();
 
@@ -140,7 +141,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
 			relationship.type = RelationshipType.blocked;
 			await relationship.save();
 		} else {
-			relationship = await Object.assign(new Relationship(), { to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save();
+			relationship = await (OrmUtils.mergeDeep(new Relationship(), { to_id: id, type: RelationshipType.blocked, from_id: req.user_id }) as Relationship).save();
 		}
 
 		if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
@@ -166,8 +167,8 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
 	const { maxFriends } = Config.get().limits.user;
 	if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
 
-	let incoming_relationship = Object.assign(new Relationship(), { nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
-	let outgoing_relationship = Object.assign(new Relationship(), {
+	let incoming_relationship = OrmUtils.mergeDeep(new Relationship(), { nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
+	let outgoing_relationship = OrmUtils.mergeDeep(new Relationship(), {
 		nickname: undefined,
 		type: RelationshipType.outgoing,
 		to: friend,
diff --git a/api/src/util/handlers/Message.ts b/api/src/util/handlers/Message.ts
index 34fb3d05..0f584c56 100644
--- a/api/src/util/handlers/Message.ts
+++ b/api/src/util/handlers/Message.ts
@@ -26,6 +26,7 @@ import { HTTPError } from "@fosscord/util";
 import fetch from "node-fetch";
 import cheerio from "cheerio";
 import { MessageCreateSchema } from "../../routes/channels/#channel_id/messages";
+import { OrmUtils } from "@fosscord/util";
 const allow_empty = false;
 // TODO: check webhook, application, system author, stickers
 // TODO: embed gifs/videos/images
@@ -47,7 +48,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
 	const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] });
 	if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
 
-	const message = Object.assign(new Message(), {
+	const message = OrmUtils.mergeDeep(new Message(), {
 		...opts,
 		sticker_items: opts.sticker_ids?.map((x) => ({ id: x })),
 		guild_id: channel.guild_id,
@@ -132,9 +133,9 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
 		}
 	}
 
-	message.mention_channels = mention_channel_ids.map((x) => Object.assign(new Channel(), { id: x }));
-	message.mention_roles = mention_role_ids.map((x) => Object.assign(new Role(), { id: x }));
-	message.mentions = mention_user_ids.map((x) => Object.assign(new User(), { id: x }));
+	message.mention_channels = mention_channel_ids.map((x) => OrmUtils.mergeDeep(new Channel(), { id: x }));
+	message.mention_roles = mention_role_ids.map((x) => OrmUtils.mergeDeep(new Role(), { id: x }));
+	message.mentions = mention_user_ids.map((x) => OrmUtils.mergeDeep(new User(), { id: x }));
 	message.mention_everyone = mention_everyone;
 
 	// TODO: check and put it all in the body
diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index c6c24fcd..ca081d02 100644
--- a/gateway/src/opcodes/Identify.ts
+++ b/gateway/src/opcodes/Identify.ts
@@ -28,6 +28,7 @@ import { IdentifySchema } from "../schema/Identify";
 const experiments: any = [];
 import { check } from "./instanceOf";
 import { Recipient } from "@fosscord/util";
+import { OrmUtils } from "@fosscord/util";
 
 // TODO: user sharding
 // TODO: check privileged intents, if defined in the config
@@ -50,7 +51,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 
 	const session_id = genSessionId();
 	this.session_id = session_id; //Set the session of the WebSocket object
-
+	
 	const [user, read_states, members, recipients, session, application] =
 		await Promise.all([
 			User.findOneOrFail({
@@ -83,7 +84,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 				// TODO: public user selection
 			}),
 			// save the session and delete it when the websocket is closed
-			Object.assign(new Session(),{
+			await OrmUtils.mergeDeep(new Session(), {
 				user_id: this.user_id,
 				session_id: session_id,
 				// TODO: check if status is only one of: online, dnd, offline, idle
diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/gateway/src/opcodes/VoiceStateUpdate.ts
index 1f7c2f1b..d62425f3 100644
--- a/gateway/src/opcodes/VoiceStateUpdate.ts
+++ b/gateway/src/opcodes/VoiceStateUpdate.ts
@@ -12,6 +12,7 @@ import {
 	VoiceState,
 	VoiceStateUpdateEvent,
 } from "@fosscord/util";
+import { OrmUtils } from "@fosscord/util";
 // TODO: check if a voice server is setup
 // Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not.
 
@@ -47,9 +48,9 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 
 		//The event send by Discord's client on channel leave has both guild_id and channel_id as null
 		if (body.guild_id === null) body.guild_id = voiceState.guild_id;
-		voiceState = Object.assign(voiceState, body);
+		voiceState = OrmUtils.mergeDeep(voiceState, body);
 	} catch (error) {
-		voiceState = Object.assign(new VoiceState(), {
+		voiceState = OrmUtils.mergeDeep(new VoiceState(), {
 			...body,
 			user_id: this.user_id,
 			deaf: false,
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index a7ca647b..48469103 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";

+import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId} from "typeorm";

+import { OrmUtils } from "@fosscord/util";

 import { BaseClass } from "./BaseClass";

 import { Guild } from "./Guild";

 import { PublicUserProjection, User } from "./User";

@@ -222,7 +223,7 @@ export class Channel extends BaseClass {
 		};

 

 		await Promise.all([

-			Object.assign(new Channel(),channel).save(),

+			OrmUtils.mergeDeep(new Channel(),channel).save(),

 			!opts?.skipEventEmit

 				? emitEvent({

 					event: "CHANNEL_CREATE",

@@ -263,7 +264,7 @@ export class Channel extends BaseClass {
 				if (containsAll(re, channelRecipients)) {

 					if (channel == null) {

 						channel = ur.channel;

-						ur = Object.assign(ur, { closed: false });

+						ur = OrmUtils.mergeDeep(ur, { closed: false });

 						await ur.save();

 					}

 				}

@@ -273,7 +274,7 @@ export class Channel extends BaseClass {
 		if (channel == null) {

 			name = trimSpecial(name);

 

-			channel = await (Object.assign(new Channel(), {

+			channel = await (OrmUtils.mergeDeep(new Channel(), {

 				name,

 				type,

 				owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server

@@ -281,7 +282,7 @@ export class Channel extends BaseClass {
 				last_message_id: null,

 				recipients: channelRecipients.map(

 					(x) =>

-						Object.assign(new Recipient(), { user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })

+						OrmUtils.mergeDeep(new Recipient(), { user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })

 				),

 			}) as Channel).save();

 		}

diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 058033e8..da8a2c2f 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId} from "typeorm";
+import { OrmUtils } from "@fosscord/util";
 import { Config, handleFile, Snowflake } from "..";
 import { Ban } from "./Ban";
 import { BaseClass } from "./BaseClass";
@@ -285,7 +286,7 @@ export class Guild extends BaseClass {
 	}) {
 		const guild_id = Snowflake.generate();
 
-		const guild: Guild = Object.assign(new Guild(),{
+		const guild: Guild = OrmUtils.mergeDeep(new Guild(),{
 			name: body.name || "Fosscord",
 			icon: await handleFile(`/icons/${guild_id}`, body.icon as string),
 			region: Config.get().regions.default,
@@ -321,7 +322,7 @@ export class Guild extends BaseClass {
 
 		// we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error
 		// TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage
-		let role: Role = Object.assign(new Role(), {
+		let role: Role = OrmUtils.mergeDeep(new Role(), {
 			id: guild_id,
 			guild_id: guild_id,
 			color: 0,
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index e4aa8331..f0c361d0 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -25,6 +25,7 @@ import { Role } from "./Role";
 import { BaseClassWithoutId } from "./BaseClass";
 import { Ban, PublicGuildRelations } from ".";
 import { DiscordApiErrors } from "../util/Constants";
+import { OrmUtils } from "@fosscord/util";
 
 export const MemberPrivateProjection: (keyof Member)[] = [
 	"id",
@@ -161,7 +162,7 @@ export class Member extends BaseClassWithoutId {
 			}),
 			Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }),
 		]);
-		member.roles.push(Object.assign(new Role(), { id: role_id }));
+		member.roles.push(OrmUtils.mergeDeep(new Role(), { id: role_id }));
 
 		await Promise.all([
 			member.save(),
@@ -256,7 +257,7 @@ export class Member extends BaseClassWithoutId {
 			nick: undefined,
 			roles: [guild_id], // @everyone role
 			joined_at: new Date(),
-			premium_since: (new Date()).getTime(),
+			premium_since: new Date(),
 			deaf: false,
 			mute: false,
 			pending: false,
@@ -264,9 +265,9 @@ export class Member extends BaseClassWithoutId {
 		//TODO: check for bugs
 		if(guild.member_count) guild.member_count++;
 		await Promise.all([
-			Object.assign(new Member(), {
+			OrmUtils.mergeDeep(new Member(), {
 				...member,
-				roles: [Object.assign(new Role(), { id: guild_id })],
+				roles: [OrmUtils.mergeDeep(new Role(), { id: guild_id })],
 				// read_state: {},
 				settings: {
 					channel_overrides: [],
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 81017c2d..d023780b 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -1,4 +1,5 @@
-import { Column, Entity, FindOneOptions, FindOptionsSelectByString, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm";
+import { Column, Entity, FindOneOptions, FindOptionsSelectByString, JoinColumn, ManyToMany, OneToMany, RelationId} from "typeorm";
+import { OrmUtils } from "@fosscord/util";
 import { BaseClass } from "./BaseClass";
 import { BitField } from "../util/BitField";
 import { Relationship } from "./Relationship";
@@ -255,7 +256,7 @@ export class User extends BaseClass {
 		// 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 user = Object.assign(new User(), {
+		const user = OrmUtils.mergeDeep(new User(), {
 			created_at: new Date(),
 			username: username,
 			discriminator,
diff --git a/util/src/util/imports/OrmUtils.ts b/util/src/util/imports/OrmUtils.ts
new file mode 100644
index 00000000..91d88172
--- /dev/null
+++ b/util/src/util/imports/OrmUtils.ts
@@ -0,0 +1,113 @@
+//source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts
+export class OrmUtils {
+    // Checks if it's an object made by Object.create(null), {} or new Object()
+    private static isPlainObject(item: any) {
+        if (item === null || item === undefined) {
+            return false
+        }
+
+        return !item.constructor || item.constructor === Object
+    }
+
+    private static mergeArrayKey(
+        target: any,
+        key: number,
+        value: any,
+        memo: Map<any, any>,
+    ) {
+        // Have we seen this before?  Prevent infinite recursion.
+        if (memo.has(value)) {
+            target[key] = memo.get(value)
+            return
+        }
+
+        if (value instanceof Promise) {
+            // Skip promises entirely.
+            // This is a hold-over from the old code & is because we don't want to pull in
+            // the lazy fields.  Ideally we'd remove these promises via another function first
+            // but for now we have to do it here.
+            return
+        }
+
+        if (!this.isPlainObject(value) && !Array.isArray(value)) {
+            target[key] = value
+            return
+        }
+
+        if (!target[key]) {
+            target[key] = Array.isArray(value) ? [] : {}
+        }
+
+        memo.set(value, target[key])
+        this.merge(target[key], value, memo)
+        memo.delete(value)
+    }
+
+    private static mergeObjectKey(
+        target: any,
+        key: string,
+        value: any,
+        memo: Map<any, any>,
+    ) {
+        // Have we seen this before?  Prevent infinite recursion.
+        if (memo.has(value)) {
+            Object.assign(target, { [key]: memo.get(value) })
+            return
+        }
+
+        if (value instanceof Promise) {
+            // Skip promises entirely.
+            // This is a hold-over from the old code & is because we don't want to pull in
+            // the lazy fields.  Ideally we'd remove these promises via another function first
+            // but for now we have to do it here.
+            return
+        }
+
+        if (!this.isPlainObject(value) && !Array.isArray(value)) {
+            Object.assign(target, { [key]: value })
+            return
+        }
+
+        if (!target[key]) {
+            Object.assign(target, { [key]: value })
+        }
+
+        memo.set(value, target[key])
+        this.merge(target[key], value, memo)
+        memo.delete(value)
+    }
+
+    private static merge(
+        target: any,
+        source: any,
+        memo: Map<any, any> = new Map(),
+    ): any {
+        if (Array.isArray(target) && Array.isArray(source)) {
+            for (let key = 0; key < source.length; key++) {
+                this.mergeArrayKey(target, key, source[key], memo)
+            }
+        }
+        else {
+            for (const key of Object.keys(source)) {
+                this.mergeObjectKey(target, key, source[key], memo)
+            }
+        }
+
+        
+    }
+
+    /**
+     * Deep Object.assign.
+     */
+    static mergeDeep(target: any, ...sources: any[]): any {
+        if (!sources.length) {
+            return target
+        }
+
+        for (const source of sources) {
+            OrmUtils.merge(target, source)
+        }
+
+        return target
+    }
+}
\ No newline at end of file
diff --git a/util/src/util/imports/index.ts b/util/src/util/imports/index.ts
index 4a4448ba..18c47a3b 100644
--- a/util/src/util/imports/index.ts
+++ b/util/src/util/imports/index.ts
@@ -1,2 +1,3 @@
 export * from './Checks';
 export * from './HTTPError';
+export * from './OrmUtils';
\ No newline at end of file