extend views with year count

This commit is contained in:
Julian Krauser 2025-01-05 16:09:39 +01:00
parent 91217d1e6e
commit d683bcb4c1
8 changed files with 133 additions and 107 deletions

View file

@ -38,71 +38,7 @@ export async function executeQuery(req: Request, res: Response): Promise<any> {
let count = parseInt((req.query.count as string) ?? "25"); let count = parseInt((req.query.count as string) ?? "25");
const query = req.body.query; const query = req.body.query;
if (typeof query == "string") { let result = await DynamicQueryBuilder.executeQuery(query, offset, count);
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",
});
}
try { res.json(result);
let data: Array<any> = [];
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,
});
}
}
} }

View file

@ -61,11 +61,11 @@ import { newsletterRecipients } from "./entity/club/newsletter/newsletterRecipie
import { Newsletter1735118780511 } from "./migrations/1735118780511-newsletter"; import { Newsletter1735118780511 } from "./migrations/1735118780511-newsletter";
import { newsletterConfig } from "./entity/settings/newsletterConfig"; import { newsletterConfig } from "./entity/settings/newsletterConfig";
import { NewsletterConfig1735207446910 } from "./migrations/1735207446910-newsletterConfig"; import { NewsletterConfig1735207446910 } from "./migrations/1735207446910-newsletterConfig";
import { TemplateMargins1735733514043 } from "./migrations/1735733514043-templateMargins";
import { InternalId1735822722235 } from "./migrations/1735822722235-internalId"; import { InternalId1735822722235 } from "./migrations/1735822722235-internalId";
import { PostalCode1735927918979 } from "./migrations/1735927918979-postalCode"; import { PostalCode1735927918979 } from "./migrations/1735927918979-postalCode";
import { ProtocolAbsent1736072179716 } from "./migrations/1736072179716-protocolAbsent"; 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({ const dataSource = new DataSource({
type: DB_TYPE as any, type: DB_TYPE as any,
@ -137,11 +137,11 @@ const dataSource = new DataSource({
TemplateUsage1734949173739, TemplateUsage1734949173739,
Newsletter1735118780511, Newsletter1735118780511,
NewsletterConfig1735207446910, NewsletterConfig1735207446910,
TemplateMargins1735733514043,
InternalId1735822722235, InternalId1735822722235,
PostalCode1735927918979, PostalCode1735927918979,
ProtocolAbsent1736072179716, ProtocolAbsent1736072179716,
Memberlist1736079005086 Memberlist1736079005086,
ExtendViewValues1736084198860,
], ],
migrationsRun: true, migrationsRun: true,
migrationsTransactionMode: "each", migrationsTransactionMode: "each",

View file

@ -252,7 +252,7 @@ export default abstract class DynamicQueryBuilder {
}); });
}); });
results = tempResults; 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 objResults = flatten(value as QueryResult, newKey);
const tempResults: Array<{ [key: string]: FieldType }> = []; const tempResults: Array<{ [key: string]: FieldType }> = [];
results.forEach((res) => { results.forEach((res) => {

View file

@ -16,6 +16,8 @@ export class TemplateUsage1734949173739 implements MigrationInterface {
{ name: "headerId", type: variableType_int, isNullable: true }, { name: "headerId", type: variableType_int, isNullable: true },
{ name: "bodyId", type: variableType_int, isNullable: true }, { name: "bodyId", type: variableType_int, isNullable: true },
{ name: "footerId", 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 true

View file

@ -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<void> {
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<void> {
await queryRunner.dropColumn("template_usage", "footerHeight");
await queryRunner.dropColumn("template_usage", "headerHeight");
}
}

View file

@ -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<void> {
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<void> {
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
);
}
}

View file

@ -14,7 +14,8 @@ import { Salutation } from "../enums/salutation";
.addSelect("member.nameaffix", "nameaffix") .addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate") .addSelect("member.birthdate", "birthdate")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") .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 { export class memberView {
@ViewColumn() @ViewColumn()
@ -40,4 +41,7 @@ export class memberView {
@ViewColumn() @ViewColumn()
ageThisYear: number; ageThisYear: number;
@ViewColumn()
exactAge: Date;
} }

View file

@ -16,6 +16,10 @@ import { Salutation } from "../enums/salutation";
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "durationInDays") .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.status", "status")
.leftJoin("membership.member", "member") .leftJoin("membership.member", "member")
.groupBy("status.id"), .groupBy("status.id"),
@ -24,6 +28,9 @@ export class membershipView {
@ViewColumn() @ViewColumn()
durationInDays: number; durationInDays: number;
@ViewColumn()
durationInYears: Date;
@ViewColumn() @ViewColumn()
status: string; status: string;