From eae7e28ab4da854d53f222d2db8a06ead8054d0d Mon Sep 17 00:00:00 2001 From: Fathia Oyinloye Date: Sun, 30 Aug 2026 20:42:30 +0000 Subject: [PATCH] feat(email-template): add indexes for common query paths Add indexes to frequently queried and foreign-key columns on the email-template entity and create a migration to apply them to existing databases. Avoid duplicate or redundant indexes while covering common WHERE, ORDER BY, JOIN, and foreign-key lookups. --- ...788076191928-add-email-template-indexes.ts | 39 ------------------- .../entities/email-template.entity.ts | 5 +++ ...805000000000-add-email-template-indexes.ts | 37 ++++++++++++++++++ 3 files changed, 42 insertions(+), 39 deletions(-) delete mode 100644 src/database/migration/1788076191928-add-email-template-indexes.ts create mode 100644 src/migrations/1805000000000-add-email-template-indexes.ts diff --git a/src/database/migration/1788076191928-add-email-template-indexes.ts b/src/database/migration/1788076191928-add-email-template-indexes.ts deleted file mode 100644 index 4240c2c4..00000000 --- a/src/database/migration/1788076191928-add-email-template-indexes.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm'; - -/** - * Adds database indexes to the `email_templates` table. - * - * These cover the common lookups/sorts on the entity: - * - `name` is used for filtering/searching by template name. - * - `createdAt` is used for ordering/pagination (e.g. most recent templates). - * - * The `key` column is intentionally NOT indexed here because it is declared - * `UNIQUE`, which already creates a unique index. The primary key `id` is also - * already covered by its own index, so no redundant indexes are introduced. - */ -export class AddEmailTemplateIndexes1788076191928 implements MigrationInterface { - name = 'AddEmailTemplateIndexes1788076191928'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.createIndex( - 'email_templates', - new TableIndex({ - name: 'IDX_email_templates_name', - columnNames: ['name'], - }), - ); - - await queryRunner.createIndex( - 'email_templates', - new TableIndex({ - name: 'IDX_email_templates_created_at', - columnNames: ['createdAt'], - }), - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropIndex('email_templates', 'IDX_email_templates_created_at'); - await queryRunner.dropIndex('email_templates', 'IDX_email_templates_name'); - } -} diff --git a/src/email-marketing/entities/email-template.entity.ts b/src/email-marketing/entities/email-template.entity.ts index 684a41c2..938a9938 100644 --- a/src/email-marketing/entities/email-template.entity.ts +++ b/src/email-marketing/entities/email-template.entity.ts @@ -6,6 +6,7 @@ import { UpdateDateColumn, DeleteDateColumn, VersionColumn, + Index, } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; @@ -22,6 +23,7 @@ export class EmailTemplate { version: number; @ApiProperty() + @Index('IDX_email_templates_name') @Column() name: string; @@ -38,6 +40,7 @@ export class EmailTemplate { textContent?: string; @ApiProperty({ required: false }) + @Index('IDX_email_templates_category') @Column({ nullable: true }) category?: string; @@ -50,10 +53,12 @@ export class EmailTemplate { thumbnailUrl?: string; @ApiProperty() + @Index('IDX_email_templates_is_active') @Column({ default: true }) isActive: boolean; @ApiProperty() + @Index('IDX_email_templates_created_at') @CreateDateColumn() createdAt: Date; diff --git a/src/migrations/1805000000000-add-email-template-indexes.ts b/src/migrations/1805000000000-add-email-template-indexes.ts new file mode 100644 index 00000000..7678c946 --- /dev/null +++ b/src/migrations/1805000000000-add-email-template-indexes.ts @@ -0,0 +1,37 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +/** + * Add the supporting indexes used by the email-template lookup and list paths. + * + * Common access patterns for this table include: + * - filtering by template `name` + * - filtering by `category` + * - filtering active/inactive templates via `isActive` + * - ordering recent templates by `createdAt` + * + * These indexes align the database schema with the entity metadata and avoid + * needing to rely on `synchronize` for existing databases. + */ +export class AddEmailTemplateIndexes1805000000000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE INDEX IF NOT EXISTS "IDX_email_templates_name" ON "email_templates" ("name")', + ); + await queryRunner.query( + 'CREATE INDEX IF NOT EXISTS "IDX_email_templates_category" ON "email_templates" ("category")', + ); + await queryRunner.query( + 'CREATE INDEX IF NOT EXISTS "IDX_email_templates_is_active" ON "email_templates" ("isActive")', + ); + await queryRunner.query( + 'CREATE INDEX IF NOT EXISTS "IDX_email_templates_created_at" ON "email_templates" ("createdAt")', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX IF EXISTS "IDX_email_templates_created_at"'); + await queryRunner.query('DROP INDEX IF EXISTS "IDX_email_templates_is_active"'); + await queryRunner.query('DROP INDEX IF EXISTS "IDX_email_templates_category"'); + await queryRunner.query('DROP INDEX IF EXISTS "IDX_email_templates_name"'); + } +}