From e2a5e2d50c1980a6a682b6a847b823cb8d88e365 Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 18 Apr 2026 00:23:14 +0200 Subject: [PATCH] Fix error logging, dont use simple-array --- src/api/middlewares/ErrorHandler.ts | 2 +- src/util/entities/Application.ts | 6 +-- src/util/entities/AutomodRule.ts | 4 +- src/util/entities/BaseClass.ts | 2 - src/util/entities/ConnectedAccount.ts | 2 +- src/util/entities/Emoji.ts | 4 +- src/util/entities/Encryption.ts | 2 +- src/util/entities/Guild.ts | 6 +-- src/util/entities/Member.ts | 2 +- src/util/entities/TeamMember.ts | 2 +- src/util/entities/User.ts | 6 +-- ...76450647000-GuildChannelOrderingAsArray.ts | 17 ++++++++ ...1776450647001-AllSimpleArraysToPgArrays.ts | 41 +++++++++++++++++++ 13 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 src/util/migration/postgres/1776450647000-GuildChannelOrderingAsArray.ts create mode 100644 src/util/migration/postgres/1776450647001-AllSimpleArraysToPgArrays.ts diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts index 02d38def1..7b85d5e1f 100644 --- a/src/api/middlewares/ErrorHandler.ts +++ b/src/api/middlewares/ErrorHandler.ts @@ -50,7 +50,7 @@ export function ErrorHandler(error: Error & { type?: string }, req: Request, res code = 50109; message = "The request body contains invalid JSON."; } else { - console.error(`[Error] ${code} ${req.url}\n`, errors, "\nbody:", req.body); + console.error(`[Error] ${code} ${req.url}\n`, errors ?? error, "\nbody:", req.body); if (req.server?.options?.production) { // don't expose internal errors to the user, instead human errors should be thrown as HTTPError diff --git a/src/util/entities/Application.ts b/src/util/entities/Application.ts index 18a694f91..c4f403436 100644 --- a/src/util/entities/Application.ts +++ b/src/util/entities/Application.ts @@ -61,7 +61,7 @@ export class Application extends BaseClass { @Column() flags: number = 0; - @Column({ type: "simple-array", nullable: true }) + @Column({ type: "varchar", nullable: true }) redirect_uris: string[] = []; @Column({ nullable: true }) @@ -92,7 +92,7 @@ export class Application extends BaseClass { @OneToOne(() => User, { onDelete: "CASCADE" }) bot?: User; - @Column({ type: "simple-array", nullable: true }) + @Column({ type: "varchar", array: true, nullable: true }) tags?: string[]; @Column({ nullable: true }) @@ -120,7 +120,7 @@ export class Application extends BaseClass { //just for us - //@Column({ type: "simple-array", nullable: true }) + //@Column({ type: "varchar", array: true, nullable: true }) //rpc_origins?: string[]; //@Column({ nullable: true }) diff --git a/src/util/entities/AutomodRule.ts b/src/util/entities/AutomodRule.ts index f57018ff9..973129848 100644 --- a/src/util/entities/AutomodRule.ts +++ b/src/util/entities/AutomodRule.ts @@ -35,10 +35,10 @@ export class AutomodRule extends BaseClass { @Column() event_type: AutomodRuleEventType; - @Column({ type: "simple-array" }) + @Column({ type: "int8", array: true }) exempt_channels: string[]; - @Column({ type: "simple-array" }) + @Column({ type: "int8", array: true }) exempt_roles: string[]; @Column() diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts index ada9e5887..bcebca0f3 100644 --- a/src/util/entities/BaseClass.ts +++ b/src/util/entities/BaseClass.ts @@ -122,5 +122,3 @@ export class BaseClass extends BaseClassWithoutId { if (!this.id) this.id = Snowflake.generate(); } } - -export const ArrayColumn = (opts: ColumnOptions) => (process.env.DATABASE?.startsWith("postgres") ? Column({ ...opts, array: true }) : Column({ ...opts, type: "simple-array" })); diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index 179f152e5..d0ed8097e 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -59,7 +59,7 @@ export class ConnectedAccount extends BaseClass { @Column({ select: false }) visibility?: number = 0; - @Column({ type: "simple-array" }) + @Column({ type: "varchar", array: true }) integrations?: string[] = []; @Column({ type: "jsonb", name: "metadata", nullable: true }) diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts index 4c22921ba..586550f94 100644 --- a/src/util/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts @@ -57,9 +57,9 @@ export class Emoji extends BaseClass { @Column() require_colons: boolean; - @Column({ type: "simple-array" }) + @Column({ type: "int8", array: true }) roles: string[]; // roles this emoji is whitelisted to (new discord feature?) - @Column({ type: "simple-array", nullable: true }) + @Column({ type: "int8", array: true, nullable: true }) groups: string[]; // user groups this emoji is whitelisted to (Spacebar extension) } diff --git a/src/util/entities/Encryption.ts b/src/util/entities/Encryption.ts index ee92bd381..9c76701a5 100644 --- a/src/util/entities/Encryption.ts +++ b/src/util/entities/Encryption.ts @@ -32,7 +32,7 @@ export class SecuritySettings extends BaseClass { @Column() encryption_permission_mask: number; - @Column({ type: "simple-array" }) + @Column({ type: "varchar", array: true }) allowed_algorithms: string[]; @Column() diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts index c38f72381..213c2c969 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -101,7 +101,7 @@ export class Guild extends BaseClass { @Column({ nullable: true }) explicit_content_filter?: number; - @Column({ type: "simple-array" }) + @Column({ type: "varchar", array: true }) features: string[] = []; //TODO use enum //TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features @@ -267,7 +267,7 @@ export class Guild extends BaseClass { @Column({ type: "jsonb" }) welcome_screen: GuildWelcomeScreen; - @Column({ nullable: true }) + @Column({ nullable: true, type: "int8" }) @RelationId((guild: Guild) => guild.widget_channel) widget_channel_id?: string; @@ -295,7 +295,7 @@ export class Guild extends BaseClass { @Column({ nullable: true }) premium_progress_bar_enabled: boolean = false; - @Column({ select: false, type: "simple-array" }) + @Column({ select: false, type: "int8", array: true }) channel_ordering: string[]; @Column() diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts index 802338ef7..8a460afeb 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts @@ -133,7 +133,7 @@ export class Member extends BaseClassWithoutId { @Column() bio: string; - @Column({ nullable: true, type: "simple-array" }) + @Column({ nullable: true, type: "int4", array: true }) theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models @Column({ nullable: true }) diff --git a/src/util/entities/TeamMember.ts b/src/util/entities/TeamMember.ts index 779a81193..72e23b764 100644 --- a/src/util/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts @@ -28,7 +28,7 @@ export class TeamMember extends BaseClass { @Column({ type: "int" }) membership_state: TeamMemberState; - @Column({ type: "simple-array" }) + @Column({ type: "varchar", array: true }) permissions: string[]; @Column() diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index e01da3af3..e2590f59d 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -61,7 +61,7 @@ export class User extends BaseClass { // TODO: Separate `User` and `UserProfile` models // puyo: changed from [number, number] because it breaks openapi - @Column({ nullable: true, type: "simple-array" }) + @Column({ nullable: true, type: "int4", array: true }) theme_colors?: number[]; @Column({ nullable: true }) @@ -166,7 +166,7 @@ export class User extends BaseClass { hash?: string; // hash of the password, salt is saved in password (bcrypt) }; - @Column({ type: "simple-array", select: false }) + @Column({ type: "varchar", array: true, select: false }) fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts @OneToOne(() => UserSettings, { @@ -180,7 +180,7 @@ export class User extends BaseClass { @OneToMany(() => SecurityKey, (key: SecurityKey) => key.user) security_keys: SecurityKey[]; - @Column({ type: "simple-array", nullable: true }) + @Column({ type: "int8", array: true, nullable: true }) badge_ids?: string[]; @Column({ type: "jsonb", nullable: true }) diff --git a/src/util/migration/postgres/1776450647000-GuildChannelOrderingAsArray.ts b/src/util/migration/postgres/1776450647000-GuildChannelOrderingAsArray.ts new file mode 100644 index 000000000..e75c3e89a --- /dev/null +++ b/src/util/migration/postgres/1776450647000-GuildChannelOrderingAsArray.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class GuildChannelOrderingAsArray1776450647000 implements MigrationInterface { + name = "GuildChannelOrderingAsArray1776450647000"; + + public async up(queryRunner: QueryRunner): Promise { + // spacebar was randomly adding json data into CSV values, unwrap them + await queryRunner.query(`UPDATE guilds SET channel_ordering = REPLACE(channel_ordering, '"', '') WHERE channel_ordering ~ '"';`); + await queryRunner.query(`UPDATE guilds SET channel_ordering = REPLACE(channel_ordering, '[', '') WHERE channel_ordering ~ '\\[';`); + await queryRunner.query(`UPDATE guilds SET channel_ordering = REPLACE(channel_ordering, ']', '') WHERE channel_ordering ~ '\\]';`); + await queryRunner.query(`ALTER TABLE guilds ALTER COLUMN channel_ordering TYPE int8[] USING string_to_array(channel_ordering, ',')::int8[];`); + } + + public async down(queryRunner: QueryRunner): Promise { + console.log(`Migration ${this.name}.down() not implemented`); + } +} diff --git a/src/util/migration/postgres/1776450647001-AllSimpleArraysToPgArrays.ts b/src/util/migration/postgres/1776450647001-AllSimpleArraysToPgArrays.ts new file mode 100644 index 000000000..44d2c5912 --- /dev/null +++ b/src/util/migration/postgres/1776450647001-AllSimpleArraysToPgArrays.ts @@ -0,0 +1,41 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AllSimpleArraysToPgArrays1776450647001 implements MigrationInterface { + name = "AllSimpleArraysToPgArrays1776450647001"; + + public async up(queryRunner: QueryRunner): Promise { + await this.cleanAndConvertToArray(queryRunner, "applications", "redirect_uris", "varchar"); + await this.cleanAndConvertToArray(queryRunner, "applications", "tags", "varchar"); + // await this.cleanAndConvertToArray(queryRunner, "applications", "rpc_origins", "varchar"); + + await this.cleanAndConvertToArray(queryRunner, "automod_rules", "exempt_channels", "int8"); + await this.cleanAndConvertToArray(queryRunner, "automod_rules", "exempt_roles", "int8"); + + await this.cleanAndConvertToArray(queryRunner, "connected_accounts", "integrations", "varchar"); + + await this.cleanAndConvertToArray(queryRunner, "emojis", "roles", "int8"); + await this.cleanAndConvertToArray(queryRunner, "emojis", "groups", "int8"); + + await this.cleanAndConvertToArray(queryRunner, "guilds", "features", "varchar"); + + await this.cleanAndConvertToArray(queryRunner, "members", "theme_colors", "int4"); + + await this.cleanAndConvertToArray(queryRunner, "team_members", "permissions", "varchar"); + + await this.cleanAndConvertToArray(queryRunner, "users", "theme_colors", "int4"); + await this.cleanAndConvertToArray(queryRunner, "users", "fingerprints", "varchar"); + await this.cleanAndConvertToArray(queryRunner, "users", "badge_ids", "int8"); + } + + public async down(queryRunner: QueryRunner): Promise { + console.log(`Migration ${this.name}.down() not implemented`); + } + + private async cleanAndConvertToArray(queryRunner: QueryRunner, table: string, column: string, type: string) { + // spacebar was randomly adding json data into CSV values, unwrap them + await queryRunner.query(`UPDATE ${table} SET ${column} = REPLACE(${column}, '"', '') WHERE ${column} ~ '"';`); + await queryRunner.query(`UPDATE ${table} SET ${column} = REPLACE(${column}, '[', '') WHERE ${column} ~ '\\[';`); + await queryRunner.query(`UPDATE ${table} SET ${column} = REPLACE(${column}, ']', '') WHERE ${column} ~ '\\]';`); + await queryRunner.query(`ALTER TABLE ${table} ALTER COLUMN ${column} TYPE ${type}[] USING string_to_array(${column}, ',')::${type}[];`); + } +}