summary refs log tree commit diff
path: root/util
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-19 19:09:13 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-19 19:09:13 +0200
commit3c8b0f43c41c13b838c2a0def20e91eeaa530c99 (patch)
treed53dc451b1119991077091f91a994bfd0ef79dd6 /util
parent:art: add orphanedRowAction and cascade onDelete to entities (diff)
parent:truck: move file handle to utility (diff)
downloadserver-3c8b0f43c41c13b838c2a0def20e91eeaa530c99.tar.xz
Merge branch 'master' into auto-delete-relations
Diffstat (limited to 'util')
-rw-r--r--util/src/dtos/DmChannelDTO.ts35
-rw-r--r--util/src/dtos/UserDTO.ts17
-rw-r--r--util/src/dtos/index.ts2
-rw-r--r--util/src/entities/Channel.ts120
-rw-r--r--util/src/entities/Recipient.ts3
-rw-r--r--util/src/entities/User.ts2
-rw-r--r--util/src/index.ts1
-rw-r--r--util/src/interfaces/Event.ts22
-rw-r--r--util/src/util/Array.ts3
-rw-r--r--util/src/util/Permissions.ts1
-rw-r--r--util/src/util/index.ts1
11 files changed, 203 insertions, 4 deletions
diff --git a/util/src/dtos/DmChannelDTO.ts b/util/src/dtos/DmChannelDTO.ts
new file mode 100644
index 00000000..8b7a18fd
--- /dev/null
+++ b/util/src/dtos/DmChannelDTO.ts
@@ -0,0 +1,35 @@
+import { MinimalPublicUserDTO } from "./UserDTO";
+import { Channel, PublicUserProjection, User } from "../entities";
+
+export class DmChannelDTO {
+	icon: string | null;
+	id: string;
+	last_message_id: string | null;
+	name: string | null;
+	origin_channel_id: string | null;
+	owner_id?: string;
+	recipients: MinimalPublicUserDTO[];
+	type: number;
+
+	static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) {
+		const obj = new DmChannelDTO()
+		obj.icon = channel.icon || null
+		obj.id = channel.id
+		obj.last_message_id = channel.last_message_id || null
+		obj.name = channel.name || null
+		obj.origin_channel_id = origin_channel_id || null
+		obj.owner_id = channel.owner_id
+		obj.type = channel.type
+		obj.recipients = (await Promise.all(channel.recipients!.filter(r => !excluded_recipients.includes(r.user_id)).map(async r => {
+			return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection })
+		}))).map(u => new MinimalPublicUserDTO(u))
+		return obj
+	}
+
+	excludedRecipients(excluded_recipients: string[]): DmChannelDTO {
+		return {
+			...this,
+			recipients: this.recipients.filter(r => !excluded_recipients.includes(r.id))
+		}
+	}
+}
\ No newline at end of file
diff --git a/util/src/dtos/UserDTO.ts b/util/src/dtos/UserDTO.ts
new file mode 100644
index 00000000..f09b5f4e
--- /dev/null
+++ b/util/src/dtos/UserDTO.ts
@@ -0,0 +1,17 @@
+import { User } from "../entities";
+
+export class MinimalPublicUserDTO {
+	avatar?: string | null;
+	discriminator: string;
+	id: string;
+	public_flags: number;
+	username: string;
+
+	constructor(user: User) {
+		this.avatar = user.avatar
+		this.discriminator = user.discriminator
+		this.id = user.id
+		this.public_flags = user.public_flags
+		this.username = user.username
+	}
+}
\ No newline at end of file
diff --git a/util/src/dtos/index.ts b/util/src/dtos/index.ts
new file mode 100644
index 00000000..13702342
--- /dev/null
+++ b/util/src/dtos/index.ts
@@ -0,0 +1,2 @@
+export * from "./DmChannelDTO";
+export * from "./UserDTO";
\ No newline at end of file
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 0196fb3e..b1f75f33 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -11,16 +11,17 @@ import {
 } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { Guild } from "./Guild";
-import { User } from "./User";
+import { PublicUserProjection, User } from "./User";
 import { HTTPError } from "lambert-server";
-import { emitEvent, getPermission, Snowflake } from "../util";
-import { ChannelCreateEvent } from "../interfaces";
+import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial } from "../util";
+import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
 import { Recipient } from "./Recipient";
 import { Message } from "./Message";
 import { ReadState } from "./ReadState";
 import { Invite } from "./Invite";
 import { VoiceState } from "./VoiceState";
 import { Webhook } from "./Webhook";
+import { DmChannelDTO } from "../dtos";
 
 export enum ChannelType {
 	GUILD_TEXT = 0, // a text channel within a server
@@ -214,6 +215,119 @@ export class Channel extends BaseClass {
 		return channel;
 	}
 
+	static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
+		recipients = recipients.unique().filter((x) => x !== creator_user_id);
+		const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
+
+		// TODO: check config for max number of recipients
+		if (otherRecipientsUsers.length !== recipients.length) {
+			throw new HTTPError("Recipient/s not found");
+		}
+
+		const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
+
+		let channel = null;
+
+		const channelRecipients = [...recipients, creator_user_id];
+
+		const userRecipients = await Recipient.find({
+			where: { user_id: creator_user_id },
+			relations: ["channel", "channel.recipients"],
+		});
+
+		for (let ur of userRecipients) {
+			let re = ur.channel.recipients!.map((r) => r.user_id);
+			if (re.length === channelRecipients.length) {
+				if (containsAll(re, channelRecipients)) {
+					if (channel == null) {
+						channel = ur.channel;
+						await ur.assign({ closed: false }).save();
+					}
+				}
+			}
+		}
+
+		if (channel == null) {
+			name = trimSpecial(name);
+
+			channel = await new Channel({
+				name,
+				type,
+				owner_id: type === ChannelType.DM ? undefined : creator_user_id,
+				created_at: new Date(),
+				last_message_id: null,
+				recipients: channelRecipients.map(
+					(x) =>
+						new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })
+				),
+			}).save();
+		}
+
+		const channel_dto = await DmChannelDTO.from(channel);
+
+		if (type === ChannelType.GROUP_DM) {
+			for (let recipient of channel.recipients!) {
+				await emitEvent({
+					event: "CHANNEL_CREATE",
+					data: channel_dto.excludedRecipients([recipient.user_id]),
+					user_id: recipient.user_id,
+				});
+			}
+		} else {
+			await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
+		}
+
+		return channel_dto.excludedRecipients([creator_user_id]);
+	}
+
+	static async removeRecipientFromChannel(channel: Channel, user_id: string) {
+		await Recipient.delete({ channel_id: channel.id, user_id: user_id });
+		channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id);
+
+		if (channel.recipients?.length === 0) {
+			await Channel.deleteChannel(channel);
+			await emitEvent({
+				event: "CHANNEL_DELETE",
+				data: await DmChannelDTO.from(channel, [user_id]),
+				user_id: user_id,
+			});
+			return;
+		}
+
+		await emitEvent({
+			event: "CHANNEL_DELETE",
+			data: await DmChannelDTO.from(channel, [user_id]),
+			user_id: user_id,
+		});
+
+		//If the owner leave we make the first recipient in the list the new owner
+		if (channel.owner_id === user_id) {
+			channel.owner_id = channel.recipients!.find((r) => r.user_id !== user_id)!.user_id; //Is there a criteria to choose the new owner?
+			await emitEvent({
+				event: "CHANNEL_UPDATE",
+				data: await DmChannelDTO.from(channel, [user_id]),
+				channel_id: channel.id,
+			});
+		}
+
+		await channel.save();
+
+		await emitEvent({
+			event: "CHANNEL_RECIPIENT_REMOVE",
+			data: {
+				channel_id: channel.id,
+				user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }),
+			},
+			channel_id: channel.id,
+		} as ChannelRecipientRemoveEvent);
+	}
+
+	static async deleteChannel(channel: Channel) {
+		await Message.delete({ channel_id: channel.id }); //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util
+		//TODO before deleting the channel we should check and delete other relations
+		await Channel.delete({ id: channel.id });
+	}
+
 	isDm() {
 		return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM;
 	}
diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts
index 2a27b29f..bb280588 100644
--- a/util/src/entities/Recipient.ts
+++ b/util/src/entities/Recipient.ts
@@ -19,5 +19,8 @@ export class Recipient extends BaseClass {
 	@ManyToOne(() => require("./User").User)
 	user: import("./User").User;
 
+	@Column({ default: false })
+	closed: boolean;
+
 	// TODO: settings/mute/nick/added at/encryption keys/read_state
 }
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 736704f8..cef88777 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -124,7 +124,7 @@ export class User extends BaseClass {
 	flags: string; // UserFlags
 
 	@Column()
-	public_flags: string;
+	public_flags: number;
 
 	@JoinColumn({ name: "relationship_ids" })
 	@OneToMany(() => Relationship, (relationship: Relationship) => relationship.from)
diff --git a/util/src/index.ts b/util/src/index.ts
index f3bd9e9b..fc00d46b 100644
--- a/util/src/index.ts
+++ b/util/src/index.ts
@@ -4,6 +4,7 @@ import "reflect-metadata";
 export * from "./util/index";
 export * from "./interfaces/index";
 export * from "./entities/index";
+export * from "./dtos/index";
 
 // import Config from "../util/Config";
 // import db, { MongooseCache, toObject } from "./util/Database";
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index aff50300..03099bbb 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -127,6 +127,22 @@ export interface ChannelPinsUpdateEvent extends Event {
 	};
 }
 
+export interface ChannelRecipientAddEvent extends Event {
+	event: "CHANNEL_RECIPIENT_ADD";
+	data: {
+		channel_id: string;
+		user: User;
+	};
+}
+
+export interface ChannelRecipientRemoveEvent extends Event {
+	event: "CHANNEL_RECIPIENT_REMOVE";
+	data: {
+		channel_id: string;
+		user: User;
+	};
+}
+
 export interface GuildCreateEvent extends Event {
 	event: "GUILD_CREATE";
 	data: Guild & {
@@ -436,6 +452,8 @@ export type EventData =
 	| ChannelUpdateEvent
 	| ChannelDeleteEvent
 	| ChannelPinsUpdateEvent
+	| ChannelRecipientAddEvent
+	| ChannelRecipientRemoveEvent
 	| GuildCreateEvent
 	| GuildUpdateEvent
 	| GuildDeleteEvent
@@ -482,6 +500,8 @@ export enum EVENTEnum {
 	ChannelUpdate = "CHANNEL_UPDATE",
 	ChannelDelete = "CHANNEL_DELETE",
 	ChannelPinsUpdate = "CHANNEL_PINS_UPDATE",
+	ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD",
+	ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE",
 	GuildCreate = "GUILD_CREATE",
 	GuildUpdate = "GUILD_UPDATE",
 	GuildDelete = "GUILD_DELETE",
@@ -525,6 +545,8 @@ export type EVENT =
 	| "CHANNEL_UPDATE"
 	| "CHANNEL_DELETE"
 	| "CHANNEL_PINS_UPDATE"
+	| "CHANNEL_RECIPIENT_ADD"
+	| "CHANNEL_RECIPIENT_REMOVE"
 	| "GUILD_CREATE"
 	| "GUILD_UPDATE"
 	| "GUILD_DELETE"
diff --git a/util/src/util/Array.ts b/util/src/util/Array.ts
new file mode 100644
index 00000000..27f7c961
--- /dev/null
+++ b/util/src/util/Array.ts
@@ -0,0 +1,3 @@
+export function containsAll(arr: any[], target: any[]) {
+	return target.every(v => arr.includes(v));
+}
\ No newline at end of file
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index 9d87253a..44852f1e 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -92,6 +92,7 @@ export class Permissions extends BitField {
 	}
 
 	overwriteChannel(overwrites: ChannelPermissionOverwrite[]) {
+		if (!overwrites) return this
 		if (!this.cache) throw new Error("permission chache not available");
 		overwrites = overwrites.filter((x) => {
 			if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true;
diff --git a/util/src/util/index.ts b/util/src/util/index.ts
index 1ae96da9..3160380f 100644
--- a/util/src/util/index.ts
+++ b/util/src/util/index.ts
@@ -13,3 +13,4 @@ export * from "./RabbitMQ";
 export * from "./Regex";
 export * from "./Snowflake";
 export * from "./String";
+export * from "./Array";