ff-admin-server/src/helpers/dynamicQueryBuilder.ts

283 lines
9.4 KiB
TypeScript
Raw Normal View History

2024-12-14 15:44:17 +01:00
import { Brackets, DataSource, NotBrackets, ObjectLiteral, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
2024-11-27 15:01:31 +01:00
import { dataSource } from "../data-source";
2024-12-27 13:17:23 +01:00
import { ConditionStructure, DynamicQueryStructure, FieldType, QueryResult } from "../type/dynamicQueries";
2024-12-12 16:33:51 +01:00
import { TableMeta } from "../type/tableMeta";
2024-11-27 15:01:31 +01:00
export default abstract class DynamicQueryBuilder {
2024-12-12 16:33:51 +01:00
public static allowedTables: Array<string> = [
"award",
"communication",
"communicationType",
"executivePosition",
"membershipStatus",
"qualification",
"member",
"memberAwards",
"memberExecutivePositions",
"memberQualifications",
"membership",
2024-12-18 12:55:03 +01:00
"memberView",
"memberExecutivePositionsView",
"memberQualificationsView",
"membershipView",
2024-12-12 16:33:51 +01:00
];
2024-11-27 15:01:31 +01:00
2024-12-12 16:33:51 +01:00
public static getTableMeta(tableName: string): TableMeta {
2024-11-27 15:01:31 +01:00
let { name, columns, relations } = dataSource.getMetadata(tableName);
const uniqueColumns = columns.map((c) => ({ column: c.propertyName, type: c.type }));
return {
tableName: name,
columns: [
...uniqueColumns,
...relations
.filter((r) => !uniqueColumns.some((c) => r.propertyName == c.column))
.map((r) => ({
column: r.propertyName,
type: r.inverseEntityMetadata?.columns.find((col) => col.propertyName === r.inverseSidePropertyPath)?.type,
})),
],
relations: relations.map((r) => ({
column: r.propertyName,
relationType: r.relationType,
referencedTableName: r.inverseEntityMetadata?.tableName,
})),
};
}
2024-12-12 16:33:51 +01:00
public static getAllTableMeta(): Array<TableMeta> {
return this.allowedTables.map((table) => this.getTableMeta(table));
}
2024-12-13 16:24:33 +01:00
public static buildQuery(
queryObj: DynamicQueryStructure,
offset: number = 0,
count: number = 25
): SelectQueryBuilder<ObjectLiteral> {
2024-12-19 10:32:28 +01:00
let affix = Math.random().toString(36).substring(2);
let query = dataSource.getRepository(queryObj.table).createQueryBuilder(`${queryObj.table}_${affix}`);
2024-12-13 16:24:33 +01:00
2024-12-19 10:32:28 +01:00
this.buildDynamicQuery(query, queryObj, affix);
2024-12-14 15:44:17 +01:00
query.offset(offset);
query.limit(count);
2024-12-13 16:24:33 +01:00
2024-12-14 15:44:17 +01:00
return query;
2024-12-13 16:24:33 +01:00
}
2024-12-14 15:44:17 +01:00
private static buildDynamicQuery(
query: SelectQueryBuilder<ObjectLiteral>,
2024-12-13 16:24:33 +01:00
queryObject: DynamicQueryStructure,
2024-12-19 10:32:28 +01:00
affix: string = "",
2024-12-13 16:24:33 +01:00
depth: number = 0
2024-12-14 15:44:17 +01:00
): void {
2024-12-19 10:32:28 +01:00
const alias = queryObject.table + "_" + affix;
2024-12-16 13:56:22 +01:00
let firstSelect = true;
let selects: Array<string> = [];
2024-12-13 16:24:33 +01:00
if (queryObject.select == "*") {
2024-12-16 13:56:22 +01:00
let meta = this.getTableMeta(queryObject.table);
let relCols = meta.relations.map((r) => r.column);
selects = meta.columns.map((c) => c.column).filter((c) => !relCols.includes(c));
2024-12-13 16:24:33 +01:00
} else {
2024-12-16 13:56:22 +01:00
selects = queryObject.select;
}
for (const select of selects) {
2024-12-17 16:52:15 +01:00
if (firstSelect && depth == 0) {
2024-12-16 13:56:22 +01:00
query.select(`${alias}.${select}`);
firstSelect = false;
} else {
2024-12-14 15:44:17 +01:00
query.addSelect(`${alias}.${select}`);
}
2024-12-13 16:24:33 +01:00
}
if (queryObject.where) {
2024-12-14 15:44:17 +01:00
this.applyWhere(query, queryObject.where, alias);
2024-12-13 16:24:33 +01:00
}
if (queryObject.join) {
2024-12-14 15:44:17 +01:00
for (const join of queryObject.join) {
2024-12-19 10:32:28 +01:00
let subaffix = Math.random().toString(36).substring(2);
2024-12-19 12:18:52 +01:00
query.leftJoin(`${alias}.${join.foreignColumn}`, join.table + "_" + subaffix);
2024-12-13 16:24:33 +01:00
2024-12-19 10:32:28 +01:00
this.buildDynamicQuery(query, join, subaffix, depth + 1);
2024-12-14 15:44:17 +01:00
}
2024-12-13 16:24:33 +01:00
}
if (queryObject.orderBy) {
queryObject.orderBy.forEach((order) => {
2024-12-14 15:44:17 +01:00
query.addOrderBy(`${alias}.${order.column}`, order.order);
2024-12-13 16:24:33 +01:00
});
}
}
2024-12-14 15:44:17 +01:00
public static applyWhere(
query: SelectQueryBuilder<ObjectLiteral> | WhereExpressionBuilder,
2024-12-13 16:24:33 +01:00
conditions: Array<ConditionStructure>,
alias: string
): void {
2024-12-14 15:44:17 +01:00
for (const condition of conditions) {
if (condition.structureType == "condition") {
2024-12-13 16:24:33 +01:00
const whereClause = this.buildConditionClause(condition, alias);
2024-12-14 15:44:17 +01:00
if (condition.concat == "_" || condition.concat == "AND") {
query.andWhere(whereClause.query, whereClause.parameters);
2024-12-13 16:24:33 +01:00
} else {
2024-12-14 15:44:17 +01:00
query.orWhere(whereClause.query, whereClause.parameters);
}
} else {
2024-12-16 17:49:30 +01:00
if (condition.concat == "_" || condition.concat == "AND") {
2024-12-14 15:44:17 +01:00
query.andWhere(
condition.invert == undefined || condition.invert == true
? new Brackets((qb) => {
this.applyWhere(qb, condition.conditions, alias);
})
: new NotBrackets((qb) => {
this.applyWhere(qb, condition.conditions, alias);
})
);
} else {
query.orWhere(
condition.invert == undefined || condition.invert == true
? new Brackets((qb) => {
this.applyWhere(qb, condition.conditions, alias);
})
: new NotBrackets((qb) => {
this.applyWhere(qb, condition.conditions, alias);
})
);
2024-12-13 16:24:33 +01:00
}
}
2024-12-14 15:44:17 +01:00
}
2024-12-13 16:24:33 +01:00
}
2024-12-14 15:44:17 +01:00
private static buildConditionClause(
condition: ConditionStructure & { structureType: "condition" },
2024-12-13 16:24:33 +01:00
alias: string
): { query: string; parameters: Record<string, unknown> } {
const parameterKey = `${alias}_${condition.column}_${Math.random().toString(36).substring(2)}`;
let query = `${alias}.${condition.column}`;
let parameters: Record<string, unknown> = {};
switch (condition.operation) {
case "eq":
query += ` = :${parameterKey}`;
parameters[parameterKey] = condition.value;
break;
case "neq":
query += ` != :${parameterKey}`;
parameters[parameterKey] = condition.value;
break;
case "lt":
query += ` < :${parameterKey}`;
parameters[parameterKey] = condition.value;
break;
case "lte":
query += ` <= :${parameterKey}`;
parameters[parameterKey] = condition.value;
break;
case "gt":
query += ` > :${parameterKey}`;
parameters[parameterKey] = condition.value;
break;
case "gte":
query += ` >= :${parameterKey}`;
parameters[parameterKey] = condition.value;
break;
case "in":
query += ` IN (:...${parameterKey})`;
parameters[parameterKey] = condition.value;
break;
case "notIn":
query += ` NOT IN (:...${parameterKey})`;
parameters[parameterKey] = condition.value;
break;
case "between":
query += ` BETWEEN :${parameterKey}_start AND :${parameterKey}_end`;
parameters[`${parameterKey}_start`] = (condition.value as { start: any }).start;
parameters[`${parameterKey}_end`] = (condition.value as { end: any }).end;
break;
case "null":
query += ` IS NULL`;
break;
case "notNull":
query += ` IS NOT NULL`;
break;
case "contains":
query += ` LIKE :${parameterKey}`;
parameters[parameterKey] = `%${condition.value}%`;
break;
case "notContains":
query += ` NOT LIKE :${parameterKey}`;
parameters[parameterKey] = `%${condition.value}%`;
break;
case "startsWith":
query += ` LIKE :${parameterKey}`;
parameters[parameterKey] = `${condition.value}%`;
break;
case "endsWith":
query += ` LIKE :${parameterKey}`;
parameters[parameterKey] = `%${condition.value}`;
break;
2024-12-17 16:52:15 +01:00
case "timespanEq":
query += ` BETWEEN :${parameterKey}_start AND :${parameterKey}_end`;
parameters[`${parameterKey}_start`] = new Date(new Date().getFullYear() - (condition.value as number), 0, 1);
parameters[`${parameterKey}_end`] = new Date(new Date().getFullYear() - (condition.value as number), 11, 31);
2024-12-13 16:24:33 +01:00
}
return { query, parameters };
}
2024-12-27 13:17:23 +01:00
public static flattenQueryResult(result: Array<QueryResult>): Array<{ [key: string]: FieldType }> {
function flatten(row: QueryResult, prefix: string = ""): Array<{ [key: string]: FieldType }> {
let results: Array<{ [key: string]: FieldType }> = [{}];
for (const key in row) {
const value = row[key];
const newKey = prefix ? `${prefix}_${key}` : key;
if (Array.isArray(value) && value.every((item) => typeof item === "object" && item !== null)) {
const arrayResults: Array<{ [key: string]: FieldType }> = [];
value.forEach((item) => {
const flattenedItems = flatten(item, newKey);
arrayResults.push(...flattenedItems);
});
const tempResults: Array<{ [key: string]: FieldType }> = [];
results.forEach((res) => {
arrayResults.forEach((arrRes) => {
tempResults.push({ ...res, ...arrRes });
});
});
results = tempResults;
} else if (value && typeof value === "object" && !Array.isArray(value)) {
const objResults = flatten(value as QueryResult, newKey);
const tempResults: Array<{ [key: string]: FieldType }> = [];
results.forEach((res) => {
objResults.forEach((objRes) => {
tempResults.push({ ...res, ...objRes });
});
});
results = tempResults;
} else {
results.forEach((res) => {
res[newKey] = String(value);
});
}
}
return results;
}
const flattenedResults: Array<{ [key: string]: FieldType }> = [];
result.forEach((item) => {
const flattenedItems = flatten(item);
flattenedResults.push(...flattenedItems);
});
return flattenedResults;
}
2024-11-27 15:01:31 +01:00
}