From d683bcb4c1fe66589a4ddb1b6418efbe88e27fc3 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sun, 5 Jan 2025 16:09:39 +0100 Subject: [PATCH] extend views with year count --- .../admin/club/queryBuilderController.ts | 68 +---------- src/data-source.ts | 8 +- src/helpers/dynamicQueryBuilder.ts | 2 +- src/migrations/1734949173739-templateUsage.ts | 2 + .../1735733514043-templateMargins.ts | 35 ------ .../1736084198860-extendViewValues.ts | 112 ++++++++++++++++++ src/views/memberView.ts | 6 +- src/views/membershipsView.ts | 7 ++ 8 files changed, 133 insertions(+), 107 deletions(-) delete mode 100644 src/migrations/1735733514043-templateMargins.ts create mode 100644 src/migrations/1736084198860-extendViewValues.ts diff --git a/src/controller/admin/club/queryBuilderController.ts b/src/controller/admin/club/queryBuilderController.ts index 5986e1c..1fa4cb5 100644 --- a/src/controller/admin/club/queryBuilderController.ts +++ b/src/controller/admin/club/queryBuilderController.ts @@ -38,71 +38,7 @@ export async function executeQuery(req: Request, res: Response): Promise { let count = parseInt((req.query.count as string) ?? "25"); const query = req.body.query; - if (typeof query == "string") { - const upperQuery = query.trim().toUpperCase(); - if (!upperQuery.startsWith("SELECT") || /INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|TRUNCATE/.test(upperQuery)) { - return res.json({ - stats: "error", - sql: query, - code: "UNALLOWED", - msg: "Not allowed to change rows", - }); - } + let result = await DynamicQueryBuilder.executeQuery(query, offset, count); - try { - let data: Array = []; - - const result = await dataSource - .transaction(async (manager) => { - data = await manager.query(query); - - throw new Error("AllwaysRollbackQuery"); - }) - .catch((error) => { - if (error.message === "AllwaysRollbackQuery") { - return { - stats: "success", - rows: data, - total: data.length, - offset: offset, - count: count, - }; - } else { - return { - stats: "error", - sql: error.sql, - code: error.code, - msg: error.sqlMessage, - }; - } - }); - res.send(result); - } catch (error) { - res.json({ - stats: "error", - sql: error.sql, - code: error.code, - msg: error.sqlMessage, - }); - } - } else { - try { - let [rows, total] = await DynamicQueryBuilder.buildQuery(query, offset, count).getManyAndCount(); - - res.json({ - stats: "success", - rows: DynamicQueryBuilder.flattenQueryResult(rows), - total: total, - offset: offset, - count: count, - }); - } catch (error) { - res.json({ - stats: "error", - sql: error.sql, - code: error.code, - msg: error.sqlMessage, - }); - } - } + res.json(result); } diff --git a/src/data-source.ts b/src/data-source.ts index f419365..7af52a0 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -61,11 +61,11 @@ import { newsletterRecipients } from "./entity/club/newsletter/newsletterRecipie import { Newsletter1735118780511 } from "./migrations/1735118780511-newsletter"; import { newsletterConfig } from "./entity/settings/newsletterConfig"; import { NewsletterConfig1735207446910 } from "./migrations/1735207446910-newsletterConfig"; -import { TemplateMargins1735733514043 } from "./migrations/1735733514043-templateMargins"; import { InternalId1735822722235 } from "./migrations/1735822722235-internalId"; import { PostalCode1735927918979 } from "./migrations/1735927918979-postalCode"; import { ProtocolAbsent1736072179716 } from "./migrations/1736072179716-protocolAbsent"; -import {Memberlist1736079005086} from "./migrations/1736079005086-memberlist"; +import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist"; +import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -137,11 +137,11 @@ const dataSource = new DataSource({ TemplateUsage1734949173739, Newsletter1735118780511, NewsletterConfig1735207446910, - TemplateMargins1735733514043, InternalId1735822722235, PostalCode1735927918979, ProtocolAbsent1736072179716, - Memberlist1736079005086 + Memberlist1736079005086, + ExtendViewValues1736084198860, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/helpers/dynamicQueryBuilder.ts b/src/helpers/dynamicQueryBuilder.ts index a97e7a2..f0962d3 100644 --- a/src/helpers/dynamicQueryBuilder.ts +++ b/src/helpers/dynamicQueryBuilder.ts @@ -252,7 +252,7 @@ export default abstract class DynamicQueryBuilder { }); }); results = tempResults; - } else if (value && typeof value === "object" && !Array.isArray(value)) { + } else if (value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) { const objResults = flatten(value as QueryResult, newKey); const tempResults: Array<{ [key: string]: FieldType }> = []; results.forEach((res) => { diff --git a/src/migrations/1734949173739-templateUsage.ts b/src/migrations/1734949173739-templateUsage.ts index bea4b51..de98006 100644 --- a/src/migrations/1734949173739-templateUsage.ts +++ b/src/migrations/1734949173739-templateUsage.ts @@ -16,6 +16,8 @@ export class TemplateUsage1734949173739 implements MigrationInterface { { name: "headerId", type: variableType_int, isNullable: true }, { name: "bodyId", type: variableType_int, isNullable: true }, { name: "footerId", type: variableType_int, isNullable: true }, + { name: "headerHeight", type: variableType_int, default: null, isNullable: true }, + { name: "footerHeight", type: variableType_int, default: null, isNullable: true }, ], }), true diff --git a/src/migrations/1735733514043-templateMargins.ts b/src/migrations/1735733514043-templateMargins.ts deleted file mode 100644 index 784d4fd..0000000 --- a/src/migrations/1735733514043-templateMargins.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; -import { DB_TYPE } from "../env.defaults"; - -export class TemplateMargins1735733514043 implements MigrationInterface { - name = "TemplateMargins1735733514043"; - - public async up(queryRunner: QueryRunner): Promise { - const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; - - await queryRunner.addColumn( - "template_usage", - new TableColumn({ - name: "headerHeight", - type: variableType_int, - default: null, - isNullable: true, - }) - ); - - await queryRunner.addColumn( - "template_usage", - new TableColumn({ - name: "footerHeight", - type: variableType_int, - default: null, - isNullable: true, - }) - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropColumn("template_usage", "footerHeight"); - await queryRunner.dropColumn("template_usage", "headerHeight"); - } -} diff --git a/src/migrations/1736084198860-extendViewValues.ts b/src/migrations/1736084198860-extendViewValues.ts new file mode 100644 index 0000000..c09064d --- /dev/null +++ b/src/migrations/1736084198860-extendViewValues.ts @@ -0,0 +1,112 @@ +import { DataSource, MigrationInterface, QueryRunner, View } from "typeorm"; +import { member } from "../entity/club/member/member"; +import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions"; +import { memberQualifications } from "../entity/club/member/memberQualifications"; +import { membership } from "../entity/club/member/membership"; + +export class ExtendViewValues1736084198860 implements MigrationInterface { + name = "ExtendViewValues1736084198860"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.dropView("membership_view"); + await queryRunner.dropView("member_view"); + + await queryRunner.createView( + new View({ + name: "member_view", + expression: (datasource: DataSource) => + datasource + .getRepository(member) + .createQueryBuilder("member") + .select("member.id", "id") + .addSelect("member.salutation", "salutation") + .addSelect("member.firstname", "firstname") + .addSelect("member.lastname", "lastname") + .addSelect("member.nameaffix", "nameaffix") + .addSelect("member.birthdate", "birthdate") + .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") + .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear") + .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"), + }), + true + ); + await queryRunner.createView( + new View({ + name: "membership_view", + expression: (datasource: DataSource) => + datasource + .getRepository(membership) + .createQueryBuilder("membership") + .select("status.id", "statusId") + .addSelect("status.status", "status") + .addSelect("member.id", "memberId") + .addSelect("member.salutation", "memberSalutation") + .addSelect("member.firstname", "memberFirstname") + .addSelect("member.lastname", "memberLastname") + .addSelect("member.nameaffix", "memberNameaffix") + .addSelect("member.birthdate", "memberBirthdate") + .addSelect( + "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", + "durationInDays" + ) + .addSelect( + "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))", + "durationInYears" + ) + .leftJoin("membership.status", "status") + .leftJoin("membership.member", "member") + .groupBy("status.id"), + }), + true + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropView("membership_view"); + await queryRunner.dropView("member_view"); + + await queryRunner.createView( + new View({ + name: "member_view", + expression: (datasource: DataSource) => + datasource + .getRepository(member) + .createQueryBuilder("member") + .select("member.id", "id") + .addSelect("member.salutation", "salutation") + .addSelect("member.firstname", "firstname") + .addSelect("member.lastname", "lastname") + .addSelect("member.nameaffix", "nameaffix") + .addSelect("member.birthdate", "birthdate") + .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") + .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"), + }), + true + ); + await queryRunner.createView( + new View({ + name: "membership_view", + expression: (datasource: DataSource) => + datasource + .getRepository(membership) + .createQueryBuilder("membership") + .select("status.id", "statusId") + .addSelect("status.status", "status") + .addSelect("member.id", "memberId") + .addSelect("member.salutation", "memberSalutation") + .addSelect("member.firstname", "memberFirstname") + .addSelect("member.lastname", "memberLastname") + .addSelect("member.nameaffix", "memberNameaffix") + .addSelect("member.birthdate", "memberBirthdate") + .addSelect( + "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", + "durationInDays" + ) + .leftJoin("membership.status", "status") + .leftJoin("membership.member", "member") + .groupBy("status.id"), + }), + true + ); + } +} diff --git a/src/views/memberView.ts b/src/views/memberView.ts index c7ab956..1743ed4 100644 --- a/src/views/memberView.ts +++ b/src/views/memberView.ts @@ -14,7 +14,8 @@ import { Salutation } from "../enums/salutation"; .addSelect("member.nameaffix", "nameaffix") .addSelect("member.birthdate", "birthdate") .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") - .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"), + .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear") + .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"), }) export class memberView { @ViewColumn() @@ -40,4 +41,7 @@ export class memberView { @ViewColumn() ageThisYear: number; + + @ViewColumn() + exactAge: Date; } diff --git a/src/views/membershipsView.ts b/src/views/membershipsView.ts index c3873cf..2beb33f 100644 --- a/src/views/membershipsView.ts +++ b/src/views/membershipsView.ts @@ -16,6 +16,10 @@ import { Salutation } from "../enums/salutation"; .addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.birthdate", "memberBirthdate") .addSelect("SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "durationInDays") + .addSelect( + "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))", + "durationInYears" + ) .leftJoin("membership.status", "status") .leftJoin("membership.member", "member") .groupBy("status.id"), @@ -24,6 +28,9 @@ export class membershipView { @ViewColumn() durationInDays: number; + @ViewColumn() + durationInYears: Date; + @ViewColumn() status: string;