diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts index 02d38de..7b85d5e 100644 --- a/src/api/middlewares/ErrorHandler.ts +++ b/src/api/middlewares/ErrorHandler.ts @@ -50,7 +50,7 @@ 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 18a694f..c4f4034 100644 --- a/src/util/entities/Application.ts +++ b/src/util/entities/Application.ts @@ -61,7 +61,7 @@ @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 @@ @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 @@ //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 f57018f..9731298 100644 --- a/src/util/entities/AutomodRule.ts +++ b/src/util/entities/AutomodRule.ts @@ -35,10 +35,10 @@ @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 ada9e58..bcebca0 100644 --- a/src/util/entities/BaseClass.ts +++ b/src/util/entities/BaseClass.ts @@ -122,5 +122,3 @@ 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 179f152..d0ed809 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -59,7 +59,7 @@ @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 4c22921..586550f 100644 --- a/src/util/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts @@ -57,9 +57,9 @@ @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 ee92bd3..9c76701 100644 --- a/src/util/entities/Encryption.ts +++ b/src/util/entities/Encryption.ts @@ -32,7 +32,7 @@ @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 c38f723..213c2c9 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -101,7 +101,7 @@ @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 @@ @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 @@ @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 802338e..8a460af 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts @@ -133,7 +133,7 @@ @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 779a811..72e23b7 100644 --- a/src/util/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts @@ -28,7 +28,7 @@ @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 e01da3a..e2590f5 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -61,7 +61,7 @@ // 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 @@ 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 @@ @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 0000000..e75c3e8 --- /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 0000000..44d2c59 --- /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}[];`); + } +}