From df231d6462aed280f35070d80a41690b157236c2 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 26 Mar 2025 09:10:08 +0100 Subject: [PATCH 1/2] change: make query id to uuid --- .../club/newsletter/newsletterCommand.ts | 2 +- .../queryStore/queryStoreCommand.ts | 4 +- .../admin/club/newsletterController.ts | 2 +- .../configuration/queryStoreController.ts | 6 +- src/data-source.ts | 8 +- src/entity/club/newsletter/newsletter.ts | 5 +- src/entity/configuration/query.ts | 4 +- src/helpers/backupHelper.ts | 8 +- src/migrations/1742922178643-queryToUUID.ts | 94 +++++++++++++++++++ .../configuration/queryStoreService.ts | 2 +- .../club/newsletter/newsletter.models.ts | 2 +- .../admin/configuration/queryStore.models.ts | 2 +- 12 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 src/migrations/1742922178643-queryToUUID.ts diff --git a/src/command/club/newsletter/newsletterCommand.ts b/src/command/club/newsletter/newsletterCommand.ts index 354ae62..8c6c5c0 100644 --- a/src/command/club/newsletter/newsletterCommand.ts +++ b/src/command/club/newsletter/newsletterCommand.ts @@ -9,7 +9,7 @@ export interface SynchronizeNewsletterCommand { newsletterTitle: string; newsletterText: string; newsletterSignatur: string; - recipientsByQueryId?: number; + recipientsByQueryId?: string; } export interface SendNewsletterCommand { diff --git a/src/command/configuration/queryStore/queryStoreCommand.ts b/src/command/configuration/queryStore/queryStoreCommand.ts index fee7ffc..1e2f667 100644 --- a/src/command/configuration/queryStore/queryStoreCommand.ts +++ b/src/command/configuration/queryStore/queryStoreCommand.ts @@ -6,10 +6,10 @@ export interface CreateQueryStoreCommand { } export interface UpdateQueryStoreCommand { - id: number; + id: string; query: string | DynamicQueryStructure; } export interface DeleteQueryStoreCommand { - id: number; + id: string; } diff --git a/src/controller/admin/club/newsletterController.ts b/src/controller/admin/club/newsletterController.ts index 68854c9..294b6e3 100644 --- a/src/controller/admin/club/newsletterController.ts +++ b/src/controller/admin/club/newsletterController.ts @@ -328,7 +328,7 @@ export async function synchronizeNewsletterById(req: Request, res: Response): Pr let newsletterTitle = req.body.newsletterTitle; let newsletterText = req.body.newsletterText; let newsletterSignatur = req.body.newsletterSignatur; - let recipientsByQueryId = req.body.recipientsByQueryId ?? null; + let recipientsByQueryId = req.body.recipientsByQueryId || null; let syncNewsletter: SynchronizeNewsletterCommand = { id, diff --git a/src/controller/admin/configuration/queryStoreController.ts b/src/controller/admin/configuration/queryStoreController.ts index f963f8d..e4c6a2d 100644 --- a/src/controller/admin/configuration/queryStoreController.ts +++ b/src/controller/admin/configuration/queryStoreController.ts @@ -27,7 +27,7 @@ export async function getAllQueryStores(req: Request, res: Response): Promise} */ export async function getQueryStoreById(req: Request, res: Response): Promise { - const id = parseInt(req.params.id); + const id = req.params.id; let queryStore = await QueryStoreService.getById(id); @@ -61,7 +61,7 @@ export async function createQueryStore(req: Request, res: Response): Promise} */ export async function updateQueryStore(req: Request, res: Response): Promise { - const id = parseInt(req.params.id); + const id = req.params.id; const query = req.body.query; let updateQueryStore: UpdateQueryStoreCommand = { @@ -81,7 +81,7 @@ export async function updateQueryStore(req: Request, res: Response): Promise} */ export async function deleteQueryStore(req: Request, res: Response): Promise { - const id = parseInt(req.params.id); + const id = req.params.id; let deleteQueryStore: DeleteQueryStoreCommand = { id: id, diff --git a/src/data-source.ts b/src/data-source.ts index aa99610..8f43314 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -48,6 +48,7 @@ import { salutation } from "./entity/configuration/salutation"; import { BackupAndResetDatabase1738166124200 } from "./migrations/1738166124200-BackupAndResetDatabase"; import { CreateSchema1738166167472 } from "./migrations/1738166167472-CreateSchema"; import { TemplatesAndProtocolSort1742549956787 } from "./migrations/1742549956787-templatesAndProtocolSort"; +import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -101,7 +102,12 @@ const dataSource = new DataSource({ webapi, webapiPermission, ], - migrations: [BackupAndResetDatabase1738166124200, CreateSchema1738166167472, TemplatesAndProtocolSort1742549956787], + migrations: [ + BackupAndResetDatabase1738166124200, + CreateSchema1738166167472, + TemplatesAndProtocolSort1742549956787, + QueryToUUID1742922178643, + ], migrationsRun: true, migrationsTransactionMode: "each", subscribers: [], diff --git a/src/entity/club/newsletter/newsletter.ts b/src/entity/club/newsletter/newsletter.ts index 11ac94a..dcdd971 100644 --- a/src/entity/club/newsletter/newsletter.ts +++ b/src/entity/club/newsletter/newsletter.ts @@ -1,6 +1,5 @@ import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm"; import { newsletterDates } from "./newsletterDates"; -import { member } from "../member/member"; import { newsletterRecipients } from "./newsletterRecipients"; import { query } from "../../configuration/query"; @@ -27,8 +26,8 @@ export class newsletter { @Column({ type: "boolean", default: false }) isSent: boolean; - @Column({ type: "int", nullable: true }) - recipientsByQueryId?: number; + @Column({ nullable: true }) + recipientsByQueryId?: string; @OneToMany(() => newsletterDates, (dates) => dates.newsletter, { cascade: ["insert"] }) dates: newsletterDates[]; diff --git a/src/entity/configuration/query.ts b/src/entity/configuration/query.ts index eb1bfe7..4eb85e9 100644 --- a/src/entity/configuration/query.ts +++ b/src/entity/configuration/query.ts @@ -2,8 +2,8 @@ import { Column, Entity, PrimaryColumn } from "typeorm"; @Entity() export class query { - @PrimaryColumn({ generated: "increment", type: "int" }) - id: number; + @PrimaryColumn({ generated: "uuid", type: "varchar" }) + id: string; @Column({ type: "varchar", length: 255, unique: true }) title: string; diff --git a/src/helpers/backupHelper.ts b/src/helpers/backupHelper.ts index 690a36f..6a8df98 100644 --- a/src/helpers/backupHelper.ts +++ b/src/helpers/backupHelper.ts @@ -212,7 +212,7 @@ export default abstract class BackupHelper { case "calendar": return await this.getCalendar(); case "query": - return await this.getQueryStore(); + return await this.getQueryStore(collectIds); case "template": return await this.getTemplate(); case "user": @@ -348,7 +348,7 @@ export default abstract class BackupHelper { "member.birthdate", "member.internalId", ]) - .addSelect(["recipientsByQuery.title", "recipientsByQuery.query"]) + .addSelect([...(collectIds ? ["query.id"] : []), "recipientsByQuery.title", "recipientsByQuery.query"]) .getMany() .then((res: any) => res.map((n: any) => ({ @@ -393,8 +393,8 @@ export default abstract class BackupHelper { .getMany(), }; } - private static async getQueryStore(): Promise> { - return await dataSource.getRepository("query").find({ select: { title: true, query: true } }); + private static async getQueryStore(collectIds: boolean): Promise> { + return await dataSource.getRepository("query").find({ select: { id: collectIds, title: true, query: true } }); } private static async getTemplate(): Promise<{ [key: string]: Array }> { return { diff --git a/src/migrations/1742922178643-queryToUUID.ts b/src/migrations/1742922178643-queryToUUID.ts new file mode 100644 index 0000000..b52990e --- /dev/null +++ b/src/migrations/1742922178643-queryToUUID.ts @@ -0,0 +1,94 @@ +import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey } from "typeorm"; +import { getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "./ormHelper"; +import { query } from "../entity/configuration/query"; + +export class QueryToUUID1742922178643 implements MigrationInterface { + name = "QueryToUUID1742922178643"; + + public async up(queryRunner: QueryRunner): Promise { + const table = await queryRunner.getTable("newsletter"); + const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1); + await queryRunner.dropForeignKey("newsletter", foreignKey); + + const entries = await queryRunner.manager.getRepository(query).find({ select: { title: true, query: true } }); + await queryRunner.clearTable("query"); + + await queryRunner.dropColumn("newsletter", "recipientsByQueryId"); + await queryRunner.dropColumn("query", "id"); + + await queryRunner.addColumn( + "query", + new TableColumn({ + name: "id", + ...getTypeByORM("uuid"), + ...isUUIDPrimary, + }) + ); + await queryRunner.addColumn( + "newsletter", + new TableColumn({ + name: "recipientsByQueryId", + ...getTypeByORM("uuid", true), + }) + ); + + await queryRunner.manager.createQueryBuilder().insert().into("query").values(entries).execute(); + + await queryRunner.createForeignKey( + "newsletter", + new TableForeignKey({ + columnNames: ["recipientsByQueryId"], + referencedColumnNames: ["id"], + referencedTableName: "query", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + const table = await queryRunner.getTable("newsletter"); + const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1); + await queryRunner.dropForeignKey("newsletter", foreignKey); + + const entries = await queryRunner.manager.getRepository(query).find({ select: { title: true, query: true } }); + await queryRunner.clearTable("query"); + + await queryRunner.dropColumn("newsletter", "recipientsByQueryId"); + await queryRunner.dropColumn("query", "id"); + + await queryRunner.addColumn( + "query", + new TableColumn({ + name: "id", + ...getTypeByORM("int"), + ...isIncrementPrimary, + }) + ); + await queryRunner.addColumn( + "newsletter", + new TableColumn({ + name: "recipientsByQueryId", + ...getTypeByORM("int", true), + }) + ); + + await queryRunner.manager + .createQueryBuilder() + .insert() + .into("query") + .values(entries.map((e, i) => ({ ...e, id: i + 1 }))) + .execute(); + + await queryRunner.createForeignKey( + "newsletter", + new TableForeignKey({ + columnNames: ["recipientsByQueryId"], + referencedColumnNames: ["id"], + referencedTableName: "query", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + } +} diff --git a/src/service/configuration/queryStoreService.ts b/src/service/configuration/queryStoreService.ts index 46bbc4c..ecce9cc 100644 --- a/src/service/configuration/queryStoreService.ts +++ b/src/service/configuration/queryStoreService.ts @@ -26,7 +26,7 @@ export default abstract class QueryStoreService { * @description get queryStore by id * @returns {Promise} */ - static async getById(id: number): Promise { + static async getById(id: string): Promise { return await dataSource .getRepository(query) .createQueryBuilder("queryStore") diff --git a/src/viewmodel/admin/club/newsletter/newsletter.models.ts b/src/viewmodel/admin/club/newsletter/newsletter.models.ts index ea42e46..37bf30c 100644 --- a/src/viewmodel/admin/club/newsletter/newsletter.models.ts +++ b/src/viewmodel/admin/club/newsletter/newsletter.models.ts @@ -8,6 +8,6 @@ export interface NewsletterViewModel { newsletterText: string; newsletterSignatur: string; isSent: boolean; - recipientsByQueryId?: number; + recipientsByQueryId?: string; recipientsByQuery?: QueryStoreViewModel; } diff --git a/src/viewmodel/admin/configuration/queryStore.models.ts b/src/viewmodel/admin/configuration/queryStore.models.ts index 2e8ce4a..22f49fd 100644 --- a/src/viewmodel/admin/configuration/queryStore.models.ts +++ b/src/viewmodel/admin/configuration/queryStore.models.ts @@ -1,7 +1,7 @@ import { DynamicQueryStructure } from "../../../type/dynamicQueries"; export interface QueryStoreViewModel { - id: number; + id: string; title: string; query: string | DynamicQueryStructure; } From 7b27c7d49ad86ea9d95ea647ab600cc78b61a775 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Fri, 28 Mar 2025 22:28:07 +0100 Subject: [PATCH 2/2] fix: check template usages being present after backup --- src/helpers/backupHelper.ts | 22 +++++++++++++++++++++- src/helpers/templateHelper.ts | 5 +++-- src/type/templateTypes.ts | 11 +++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/type/templateTypes.ts diff --git a/src/helpers/backupHelper.ts b/src/helpers/backupHelper.ts index 6a8df98..8a6191f 100644 --- a/src/helpers/backupHelper.ts +++ b/src/helpers/backupHelper.ts @@ -6,6 +6,7 @@ import InternalException from "../exceptions/internalException"; import UserService from "../service/management/userService"; import { BACKUP_COPIES, BACKUP_INTERVAL } from "../env.defaults"; import DatabaseActionException from "../exceptions/databaseActionException"; +import { availableTemplates } from "../type/templateTypes"; export type BackupSection = | "member" @@ -743,11 +744,30 @@ export default abstract class BackupHelper { .values(data?.["template"] ?? []) .orIgnore() .execute(); + + let templates = await this.transactionManager.getRepository("template").find(); + let dataWithMappedId = (data?.["template_usage"] ?? []) + .filter((d) => availableTemplates.includes(d.scope)) + .map((d) => ({ + ...d, + headerHeightId: templates.find((template) => template.template == d.headerHeight.template)?.id ?? null, + footerHeightId: templates.find((template) => template.template == d.footerHeight.template)?.id ?? null, + headerId: templates.find((template) => template.template == d.header.template)?.id ?? null, + bodyId: templates.find((template) => template.template == d.body.template)?.id ?? null, + footerId: templates.find((template) => template.template == d.footer.template)?.id ?? null, + })); + availableTemplates.forEach((at) => { + if (!dataWithMappedId.some((d) => d.scope == at)) { + dataWithMappedId.push({ + scope: at, + }); + } + }); await this.transactionManager .createQueryBuilder() .insert() .into("template_usage") - .values(data?.["template_usage"] ?? []) + .values(dataWithMappedId) .orIgnore() .execute(); } diff --git a/src/helpers/templateHelper.ts b/src/helpers/templateHelper.ts index e75c87c..b8d97f1 100644 --- a/src/helpers/templateHelper.ts +++ b/src/helpers/templateHelper.ts @@ -3,6 +3,7 @@ import { PermissionModule } from "../type/permissionTypes"; import TemplateUsageService from "../service/configuration/templateUsageService"; import Handlebars, { template } from "handlebars"; import { FileSystemHelper } from "./fileSystemHelper"; +import { TemplateFormat } from "../type/templateTypes"; export abstract class TemplateHelper { static getTemplateFromFile(template: string) { @@ -40,7 +41,7 @@ export abstract class TemplateHelper { bodyData = {}, footerData = {}, }: { - module: `${PermissionModule}` | `${PermissionModule}.${string}`; + module: TemplateFormat; title?: string; headerData?: any; bodyData?: any; @@ -88,7 +89,7 @@ export abstract class TemplateHelper { footerData = {}, customTemplate, }: { - module: `${PermissionModule}` | `${PermissionModule}.${string}`; + module: TemplateFormat; title?: string; headerData?: any; bodyData?: any; diff --git a/src/type/templateTypes.ts b/src/type/templateTypes.ts new file mode 100644 index 0000000..cede2bb --- /dev/null +++ b/src/type/templateTypes.ts @@ -0,0 +1,11 @@ +import { PermissionModule } from "./permissionTypes"; + +export type TemplateFormat = `${PermissionModule}` | `${PermissionModule}.${string}`; + +export const availableTemplates: Array = [ + "member", + "listprint", + "listprint.member", + "newsletter", + "protocol", +];