diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts
index 70dd3994..3f434f5e 100644
--- a/api/src/routes/channels/#channel_id/index.ts
+++ b/api/src/routes/channels/#channel_id/index.ts
@@ -1,4 +1,4 @@
-import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util";
+import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util";
import { Request, Response, Router } from "express";
import { handleFile, route } from "@fosscord/api";
@@ -28,7 +28,7 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request
]);
} else if (channel.type === ChannelType.GROUP_DM) {
- await ChannelService.removeRecipientFromChannel(channel, req.user_id)
+ await Channel.removeRecipientFromChannel(channel, req.user_id)
} else {
//TODO messages in this channel should be deleted before deleting the channel
await Promise.all([
diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts
index bb610a6a..cde14164 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -1,5 +1,5 @@
import { Router, Response, Request } from "express";
-import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent } from "@fosscord/util";
+import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent, Recipient } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { handleMessage, postHandleMessage, route } from "@fosscord/api";
import multer from "multer";
@@ -150,7 +150,6 @@ router.post(
return res.status(400).json(error);
}
}
- //TODO querying the DB at every message post should be avoided, caching maybe?
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] })
const embeds = [];
@@ -184,7 +183,8 @@ router.post(
}
}
- await Promise.all(channel.recipients!.map(async r => {
+ //Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
+ await Promise.all(channel.recipients!.filter(r => r.closed).map(async r => {
r.closed = false;
return await r.save()
}));
diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts
index d88b38f3..c7beeee8 100644
--- a/api/src/routes/channels/#channel_id/recipients.ts
+++ b/api/src/routes/channels/#channel_id/recipients.ts
@@ -1,5 +1,5 @@
import { Request, Response, Router } from "express";
-import { Channel, ChannelRecipientAddEvent, ChannelService, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";
+import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";
const router: Router = Router();
@@ -13,7 +13,7 @@ router.put("/:user_id", async (req: Request, res: Response) => {
user_id
].unique()
- const new_channel = await ChannelService.createDMChannel(recipients, req.user_id)
+ const new_channel = await Channel.createDMChannel(recipients, req.user_id)
return res.status(201).json(new_channel);
} else {
if (channel.recipients!.map(r => r.user_id).includes(user_id)) {
@@ -49,7 +49,7 @@ router.delete("/:user_id", async (req: Request, res: Response) => {
throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error?
}
- await ChannelService.removeRecipientFromChannel(channel, user_id)
+ await Channel.removeRecipientFromChannel(channel, user_id)
return res.sendStatus(204);
});
diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts
index 873ff245..b5782eca 100644
--- a/api/src/routes/users/@me/channels.ts
+++ b/api/src/routes/users/@me/channels.ts
@@ -1,5 +1,5 @@
import { Request, Response, Router } from "express";
-import { Recipient, ChannelService, DmChannelDTO } from "@fosscord/util";
+import { Recipient, DmChannelDTO, Channel } from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -16,7 +16,7 @@ export interface DmChannelCreateSchema {
router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema;
- res.json(await ChannelService.createDMChannel(body.recipients, req.user_id, body.name));
+ res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
});
export default router;
diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts
index 35841312..ae13cca7 100644
--- a/gateway/src/listener/listener.ts
+++ b/gateway/src/listener/listener.ts
@@ -32,7 +32,7 @@ export async function setupListener(this: WebSocket) {
});
const guilds = members.map((x) => x.guild);
const recipients = await Recipient.find({
- where: { user_id: this.user_id },
+ where: { user_id: this.user_id, closed: false },
relations: ["channel"],
});
const dm_channels = recipients.map((x) => x.channel);
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index aa2bfab3..ea632778 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,11 +1,13 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } 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 { DmChannelDTO } from "../dtos";
+import { Message } from "./Message";
export enum ChannelType {
GUILD_TEXT = 0, // a text channel within a server
@@ -97,7 +99,6 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
topic?: string;
- // TODO: DM channel
static async createChannel(
channel: Partial<Channel>,
user_id: string = "0",
@@ -150,16 +151,123 @@ export class Channel extends BaseClass {
new Channel(channel).save(),
!opts?.skipEventEmit
? emitEvent({
- event: "CHANNEL_CREATE",
- data: channel,
- guild_id: channel.guild_id,
- } as ChannelCreateEvent)
+ event: "CHANNEL_CREATE",
+ data: channel,
+ guild_id: channel.guild_id,
+ } as ChannelCreateEvent)
: Promise.resolve(),
]);
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/index.ts b/util/src/index.ts
index 538bfdd1..fc00d46b 100644
--- a/util/src/index.ts
+++ b/util/src/index.ts
@@ -4,7 +4,6 @@ import "reflect-metadata";
export * from "./util/index";
export * from "./interfaces/index";
export * from "./entities/index";
-export * from "./services/index";
export * from "./dtos/index";
// import Config from "../util/Config";
diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts
deleted file mode 100644
index aa021a4a..00000000
--- a/util/src/services/ChannelService.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Channel, ChannelType, Message, PublicUserProjection, Recipient, User } from "../entities";
-import { HTTPError } from "lambert-server";
-import { emitEvent, trimSpecial } from "../util";
-import { DmChannelDTO } from "../dtos";
-import { ChannelRecipientRemoveEvent } from "../interfaces";
-
-export function checker(arr: any[], target: any[]) {
- return target.every(v => arr.includes(v));
-}
-
-export class ChannelService {
- public 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 (checker(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])
- }
-
- public 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 ChannelService.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);
- }
-
- public 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 })
- }
-}
diff --git a/util/src/services/index.ts b/util/src/services/index.ts
deleted file mode 100644
index c012a208..00000000
--- a/util/src/services/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./ChannelService";
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/index.ts b/util/src/util/index.ts
index 4e92f017..2de26fc7 100644
--- a/util/src/util/index.ts
+++ b/util/src/util/index.ts
@@ -12,3 +12,4 @@ export * from "./RabbitMQ";
export * from "./Regex";
export * from "./Snowflake";
export * from "./String";
+export * from "./Array";
|