diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
index b5220fab..7f7de264 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -3,7 +3,6 @@ import { Router, Response, Request } from "express";
import { route } from "@fosscord/api";
import { handleMessage, postHandleMessage } from "@fosscord/api";
import { MessageCreateSchema } from "../index";
-import { deleteMessageAttachments } from "@fosscord/api/util/Attachments";
const router = Router();
// TODO: message content/embed string length limit
@@ -34,7 +33,6 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE
});
await Promise.all([
- await deleteMessageAttachments(message_id, new_message.attachments), //This delete all the attachments not in the array
new_message!.save(),
await emitEvent({
event: "MESSAGE_UPDATE",
@@ -60,7 +58,6 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
permission.hasThrow("MANAGE_MESSAGES");
}
- await deleteMessageAttachments(message_id);
await Message.delete({ id: message_id });
await emitEvent({
diff --git a/api/src/util/Attachments.ts b/api/src/util/Attachments.ts
deleted file mode 100644
index addda97f..00000000
--- a/api/src/util/Attachments.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Attachment } from "@fosscord/util";
-import { deleteFile } from "@fosscord/api";
-import { URL } from "url";
-
-export async function deleteMessageAttachments(messageId: string, keep?: Attachment[]) {
- let attachments = await Attachment.find({ message_id: messageId });
- if (keep)
- attachments = attachments.filter(x => !keep.map(k => k.id).includes(x.id));
- await Promise.all(attachments.map(a => a.remove()));
-
- attachments.forEach(a => deleteFile((new URL(a.url)).pathname)); //We don't need to await since this is done on the cdn
-}
diff --git a/api/src/util/index.ts b/api/src/util/index.ts
index 4b1e8e77..3e47ce4e 100644
--- a/api/src/util/index.ts
+++ b/api/src/util/index.ts
@@ -1,5 +1,4 @@
export * from "./Base64";
-export * from "./cdn";
export * from "./FieldError";
export * from "./ipAddress";
export * from "./Message";
diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts
index ca893400..82c5ecf5 100644
--- a/util/src/entities/Attachment.ts
+++ b/util/src/entities/Attachment.ts
@@ -1,4 +1,6 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { URL } from "url";
+import { deleteFile } from "../util/cdn";
import { BaseClass } from "./BaseClass";
@Entity("attachments")
@@ -31,4 +33,9 @@ export class Attachment extends BaseClass {
@JoinColumn({ name: "message_id" })
@ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments)
message: import("./Message").Message;
+
+ @BeforeRemove()
+ onDelete() {
+ return deleteFile(new URL(this.url).pathname);
+ }
}
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index ea632778..b1f75f33 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,4 +1,14 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ FindConditions,
+ JoinColumn,
+ ManyToOne,
+ ObjectID,
+ OneToMany,
+ RelationId,
+ RemoveOptions,
+} from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { PublicUserProjection, User } from "./User";
@@ -6,8 +16,12 @@ import { HTTPError } from "lambert-server";
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";
+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
@@ -32,13 +46,17 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
name?: string;
- @Column({ type: 'text', nullable: true })
+ @Column({ type: "text", nullable: true })
icon?: string | null;
@Column({ type: "simple-enum", enum: ChannelType })
type: ChannelType;
- @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { cascade: true })
+ @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
recipients?: Recipient[];
@Column({ nullable: true })
@@ -99,6 +117,42 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
topic?: string;
+ @OneToMany(() => Invite, (invite: Invite) => invite.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
+ invites?: Invite[];
+
+ @OneToMany(() => Message, (message: Message) => message.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
+ messages?: Message[];
+
+ @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
+ voice_states?: VoiceState[];
+
+ @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
+ read_states?: ReadState[];
+
+ @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
+ webhooks?: Webhook[];
+
+ // TODO: DM channel
static async createChannel(
channel: Partial<Channel>,
user_id: string = "0",
@@ -151,10 +205,10 @@ 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(),
]);
@@ -174,17 +228,20 @@ export class Channel extends BaseClass {
let channel = null;
- const channelRecipients = [...recipients, creator_user_id]
+ const channelRecipients = [...recipients, creator_user_id];
- const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] })
+ 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)
+ 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()
+ channel = ur.channel;
+ await ur.assign({ closed: false }).save();
}
}
}
@@ -196,80 +253,83 @@ export class Channel extends BaseClass {
channel = await new Channel({
name,
type,
- owner_id: (type === ChannelType.DM ? undefined : creator_user_id),
+ 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) })),
+ 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)
+ 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
- })
+ 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])
+ 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)
+ 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
+ user_id: user_id,
});
- return
+ return;
}
await emitEvent({
event: "CHANNEL_DELETE",
data: await DmChannelDTO.from(channel, [user_id]),
- user_id: 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?
+ 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
+ channel_id: channel.id,
});
}
- await channel.save()
+ await channel.save();
await emitEvent({
- event: "CHANNEL_RECIPIENT_REMOVE", data: {
+ event: "CHANNEL_RECIPIENT_REMOVE",
+ data: {
channel_id: channel.id,
- user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
- }, 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
+ 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 })
+ await Channel.delete({ id: channel.id });
}
isDm() {
- return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM
+ return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM;
}
}
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 7b5d2908..d46d2161 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -81,7 +81,11 @@ export class Guild extends BaseClass {
// application?: string;
@JoinColumn({ name: "ban_ids" })
- @OneToMany(() => Ban, (ban: Ban) => ban.guild)
+ @OneToMany(() => Ban, (ban: Ban) => ban.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
bans: Ban[];
@Column({ nullable: true })
@@ -124,15 +128,27 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
presence_count?: number; // users online
- @OneToMany(() => Member, (member: Member) => member.guild)
+ @OneToMany(() => Member, (member: Member) => member.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
members: Member[];
@JoinColumn({ name: "role_ids" })
- @OneToMany(() => Role, (role: Role) => role.guild)
+ @OneToMany(() => Role, (role: Role) => role.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
roles: Role[];
@JoinColumn({ name: "channel_ids" })
- @OneToMany(() => Channel, (channel: Channel) => channel.guild)
+ @OneToMany(() => Channel, (channel: Channel) => channel.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
channels: Channel[];
@Column({ nullable: true })
@@ -144,23 +160,43 @@ export class Guild extends BaseClass {
template: Template;
@JoinColumn({ name: "emoji_ids" })
- @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild)
+ @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
emojis: Emoji[];
@JoinColumn({ name: "sticker_ids" })
- @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild)
+ @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
stickers: Sticker[];
@JoinColumn({ name: "invite_ids" })
- @OneToMany(() => Invite, (invite: Invite) => invite.guild)
+ @OneToMany(() => Invite, (invite: Invite) => invite.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
invites: Invite[];
@JoinColumn({ name: "voice_state_ids" })
- @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild)
+ @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
voice_states: VoiceState[];
@JoinColumn({ name: "webhook_ids" })
- @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild)
+ @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
webhooks: Webhook[];
@Column({ nullable: true })
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 506db71a..0712f545 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -8,12 +8,14 @@ import {
Column,
CreateDateColumn,
Entity,
+ FindConditions,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
RelationId,
+ RemoveOptions,
UpdateDateColumn,
} from "typeorm";
import { BaseClass } from "./BaseClass";
@@ -112,7 +114,7 @@ export class Message extends BaseClass {
mention_everyone?: boolean;
@JoinTable({ name: "message_user_mentions" })
- @ManyToMany(() => User)
+ @ManyToMany(() => User, { orphanedRowAction: "delete", onDelete: "CASCADE", cascade: true })
mentions: User[];
@JoinTable({ name: "message_role_mentions" })
@@ -127,8 +129,11 @@ export class Message extends BaseClass {
@ManyToMany(() => Sticker)
sticker_items?: Sticker[];
- @JoinColumn({ name: "attachment_ids" })
- @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true })
+ @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
attachments?: Attachment[];
@Column({ type: "simple-json" })
|