diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts index 2ff56a7a4..97040fd3d 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts @@ -486,7 +486,7 @@ export async function postHandleMessage(message: Message) { const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown const linkMatches = content?.match(LINK_REGEX) || []; - + message.clean_data(); const data = { ...message }; const currentNormalizedUrls = new Set(); diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts index 1aaf1479d..a8844630e 100644 --- a/src/util/entities/BaseClass.ts +++ b/src/util/entities/BaseClass.ts @@ -20,12 +20,40 @@ import { BaseEntity, BeforeInsert, BeforeUpdate, Column, ColumnOptions, FindOpti import { Snowflake } from "../util/Snowflake"; import { getDatabase } from "../util/Database"; import { OrmUtils } from "../imports/OrmUtils"; +import { annotationsKey } from "../util/Decorators"; export class BaseClassWithoutId extends BaseEntity { private get construct() { return this.constructor; } + // stores custom annotations we may stick on the properties + [annotationsKey]: { [p: string]: string[] }; + + // retrieves the custom annotations as its not super straight forward + get_annotations() { + return Object.getPrototypeOf(this)[annotationsKey]; + } + + // Loops through all the keys and compares it to annotations. If the RemoveEmpty is there it sets the value to undefined if null + clean_data() { + const annotations = this.get_annotations(); + for (const key in this) { + if ( + key in this && // This object has this property, should never fail but better to be safe + key in annotations && // If this property has an annotation + annotations[key].indexOf("JsonRemoveEmpty") > -1 && // if one of the annotations is JsonRemoveEmpty + (this[key] == null || // If this property is null + (typeof this[key] == "object" && Object.keys(this[key]).length == 0)) + ) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + this[key] = undefined; // set to undefined to remove + } + } + return this; + } + private get metadata() { return getDatabase()?.getMetadata(this.construct); } diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts index 713646739..a54ab7d57 100644 --- a/src/util/entities/Message.ts +++ b/src/util/entities/Message.ts @@ -31,6 +31,7 @@ import { Attachment } from "./Attachment"; import { NewUrlUserSignatureData } from "../Signing"; import { ActionRowComponent, ApplicationCommandType, Embed, MessageSnapshot, MessageType, PartialMessage, Poll, Reaction } from "@spacebar/schemas"; import { MessageFlags } from "@spacebar/util"; +import { JsonRemoveEmpty } from "../util/Decorators"; @Entity({ name: "messages", @@ -122,14 +123,17 @@ export class Message extends BaseClass { mention_everyone?: boolean; @JoinTable({ name: "message_user_mentions" }) + @JsonRemoveEmpty @ManyToMany(() => User) mentions: User[]; @JoinTable({ name: "message_role_mentions" }) + @JsonRemoveEmpty @ManyToMany(() => Role) mention_roles: Role[]; @JoinTable({ name: "message_channel_mentions" }) + @JsonRemoveEmpty @ManyToMany(() => Channel) mention_channels: Channel[]; @@ -141,12 +145,15 @@ export class Message extends BaseClass { cascade: true, orphanedRowAction: "delete", }) + @JsonRemoveEmpty attachments?: Attachment[]; @Column({ type: "simple-json" }) + @JsonRemoveEmpty embeds: Embed[]; @Column({ type: "simple-json" }) + @JsonRemoveEmpty reactions: Reaction[]; @Column({ type: "text", nullable: true }) @@ -201,9 +208,11 @@ export class Message extends BaseClass { }; @Column({ type: "simple-json", nullable: true }) + @JsonRemoveEmpty components?: ActionRowComponent[]; @Column({ type: "simple-json", nullable: true }) + @JsonRemoveEmpty poll?: Poll; @Column({ nullable: true }) diff --git a/src/util/util/Decorators.ts b/src/util/util/Decorators.ts new file mode 100644 index 000000000..9de9c1c95 --- /dev/null +++ b/src/util/util/Decorators.ts @@ -0,0 +1,21 @@ +import { BaseClassWithoutId } from "@spacebar/util*"; + +export const annotationsKey = Symbol("Annotations"); + +// Generates an array using the annotationsKey as a property on a class when annotated with the below decorators +export function initAnnotationMetadata(target: BaseClassWithoutId, propertyKey: string) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + target[annotationsKey] || (target[annotationsKey] = {}); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + target[annotationsKey][propertyKey] || (target[annotationsKey][propertyKey] = []); +} + +// Adds to the generated array on the class with the annotation added. +export function addAnnotationMetadata(target: BaseClassWithoutId, propertyKey: string, annotation: string) { + target[annotationsKey] = { ...target[annotationsKey], [propertyKey]: [...target[annotationsKey][propertyKey], annotation] }; +} + +export function JsonRemoveEmpty(target: BaseClassWithoutId, propertyKey: string) { + initAnnotationMetadata(target, propertyKey); + addAnnotationMetadata(target, propertyKey, "JsonRemoveEmpty"); +}