@galadrim/galadmin 0.2.12 → 0.3.0-alpha.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/report/blockers.json +1 -0
- package/.agents/report/failures/2026-09-23-api-user-filter-contract.md +31 -0
- package/.agents/report/failures/2026-09-23-api-values-contract.md +25 -0
- package/.agents/report/failures/2026-10-01-cycle-02-create-callback-contract.md +17 -0
- package/.agents/report/failures/2026-10-01-cycle-06-delete-callback-id-contract.md +17 -0
- package/.agents/report/failures/2026-10-01-cycle-07-panel-delete-callback-id-contract.md +24 -0
- package/.agents/report/failures/2026-10-01-cycle-13-update-callback-id-contract.md +17 -0
- package/.agents/report/failures/2026-10-01-cycle-15-rich-text-upload-boundary-contract.md +24 -0
- package/.agents/report/failures/2026-10-03-cycle-06-record-import-parser-contract.md +11 -0
- package/.agents/report/failures/2026-10-03-cycle-07-panel-import-parser-contract.md +24 -0
- package/.agents/report/failures/2026-10-07-resume-cycle-02-api-assembly-contract.md +46 -0
- package/.agents/report/port/2026-09-23-api-config-enrichment.md +13 -0
- package/.agents/report/port/2026-09-23-api-file-upload.md +11 -0
- package/.agents/report/port/2026-09-23-api-query-authorization.md +14 -0
- package/.agents/report/port/2026-09-23-api-request-configuration.md +11 -0
- package/.agents/report/port/2026-09-23-database-manager.md +13 -0
- package/.agents/report/port/2026-10-01-api-adjacent-record-id.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-01-api-config-route.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-03-custom-action.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-04-custom-component.md +7 -0
- package/.agents/report/port/2026-10-01-cycle-05-custom-query.md +7 -0
- package/.agents/report/port/2026-10-01-cycle-08-bulk-delete.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-09-duplicate-record.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-10-notification-badges.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-11-record.md +7 -0
- package/.agents/report/port/2026-10-01-cycle-12-search-companies.md +11 -0
- package/.agents/report/port/2026-10-01-cycle-14-upload-file.md +7 -0
- package/.agents/report/port/2026-10-03-cycle-01-create-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-02-delete-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-03-panel-delete-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-04-update-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-05-api-values.md +11 -0
- package/.agents/report/port/2026-10-06-cycle-01-record-import.md +9 -0
- package/.agents/report/port/2026-10-06-cycle-03-rich-text-upload.md +7 -0
- package/.agents/report/port/2026-10-06-cycle-04-user-filters.md +11 -0
- package/.agents/report/port/2026-10-06-resume-cycle-01-panel-import.md +9 -0
- package/.agents/report/port/2026-10-07-cycle-01-api-assembly.md +16 -0
- package/.agents/report/port/2026-10-07-cycle-02-index-entry-point.md +13 -0
- package/.agents/report/remediation/2026-10-02-api-user-filter-remediation.md +71 -0
- package/.agents/report/remediation/2026-10-02-api-values-remediation.md +17 -0
- package/.agents/report/remediation/2026-10-03-callback-id-contract-remediation.md +36 -0
- package/.agents/report/remediation/2026-10-06-import-parser-remediation.md +41 -0
- package/.agents/report/remediation/2026-10-06-rich-text-upload-remediation.md +33 -0
- package/.agents/report/remediation/2026-10-07-api-assembly-remediation.md +32 -0
- package/.agents/skills/port-javascript-to-typescript/SKILL.md +42 -0
- package/.agents/skills/port-javascript-to-typescript/agents/openai.yaml +3 -0
- package/.agents/skills/port-javascript-to-typescript/references/blockers.md +31 -0
- package/.agents/skills/port-javascript-to-typescript/references/planning.md +43 -0
- package/.agents/skills/port-javascript-to-typescript/references/porting.md +9 -0
- package/.agents/skills/port-javascript-to-typescript/references/reporting.md +24 -0
- package/.agents/skills/port-javascript-to-typescript/references/rules.md +8 -0
- package/.agents/skills/port-javascript-to-typescript/references/selection.md +16 -0
- package/.agents/skills/port-javascript-to-typescript/references/tools.md +20 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/check.sh +17 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/graph.sh +30 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/madge.json +12 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/oxlint.json +14 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/test-tools.cjs +76 -0
- package/AGENTS.md +5 -0
- package/dist/api/authorization.d.ts +38 -0
- package/dist/api/authorization.js +74 -0
- package/dist/api/authorization.js.map +1 -0
- package/dist/api/config.d.ts +42 -0
- package/dist/api/config.js +105 -0
- package/dist/api/config.js.map +1 -0
- package/dist/api/filterUser.d.ts +2 -0
- package/dist/api/filterUser.js +24 -0
- package/dist/api/filterUser.js.map +1 -0
- package/dist/api/importRecords.d.ts +1 -0
- package/dist/api/importRecords.js +16 -0
- package/dist/api/importRecords.js.map +1 -0
- package/dist/api/queries.d.ts +48 -0
- package/dist/api/queries.js +139 -0
- package/dist/api/queries.js.map +1 -0
- package/dist/api/request.d.ts +29 -0
- package/dist/api/request.js +3 -0
- package/dist/api/request.js.map +1 -0
- package/dist/api/routes/adjacentRecordId.d.ts +10 -0
- package/dist/api/routes/adjacentRecordId.js +13 -0
- package/dist/api/routes/adjacentRecordId.js.map +1 -0
- package/dist/api/routes/config.d.ts +3 -0
- package/dist/api/routes/config.js +27 -0
- package/dist/api/routes/config.js.map +1 -0
- package/dist/api/routes/createRecord.d.ts +19 -0
- package/dist/api/routes/createRecord.js +93 -0
- package/dist/api/routes/createRecord.js.map +1 -0
- package/dist/api/routes/customAction.d.ts +17 -0
- package/dist/api/routes/customAction.js +35 -0
- package/dist/api/routes/customAction.js.map +1 -0
- package/dist/api/routes/customComponent.d.ts +3 -0
- package/dist/api/routes/customComponent.js +34 -0
- package/dist/api/routes/customComponent.js.map +1 -0
- package/dist/api/routes/customQuery.d.ts +7 -0
- package/dist/api/routes/customQuery.js +33 -0
- package/dist/api/routes/customQuery.js.map +1 -0
- package/dist/api/routes/deleteRecord.d.ts +9 -0
- package/dist/api/routes/deleteRecord.js +46 -0
- package/dist/api/routes/deleteRecord.js.map +1 -0
- package/dist/api/routes/deleteRecordFromPanel.d.ts +10 -0
- package/dist/api/routes/deleteRecordFromPanel.js +36 -0
- package/dist/api/routes/deleteRecordFromPanel.js.map +1 -0
- package/dist/api/routes/deleteRecords.d.ts +9 -0
- package/dist/api/routes/deleteRecords.js +12 -0
- package/dist/api/routes/deleteRecords.js.map +1 -0
- package/dist/api/routes/duplicateRecord.d.ts +17 -0
- package/dist/api/routes/duplicateRecord.js +55 -0
- package/dist/api/routes/duplicateRecord.js.map +1 -0
- package/dist/api/routes/foreignSelectRecords.d.ts +10 -0
- package/dist/api/routes/foreignSelectRecords.js +61 -0
- package/dist/api/routes/foreignSelectRecords.js.map +1 -0
- package/dist/api/routes/notificationBadges.d.ts +6 -0
- package/dist/api/routes/notificationBadges.js +31 -0
- package/dist/api/routes/notificationBadges.js.map +1 -0
- package/dist/api/routes/record.d.ts +7 -0
- package/dist/api/routes/record.js +32 -0
- package/dist/api/routes/record.js.map +1 -0
- package/dist/api/routes/records.d.ts +14 -0
- package/dist/api/routes/records.js +57 -0
- package/dist/api/routes/records.js.map +1 -0
- package/dist/api/routes/recordsExport.d.ts +13 -0
- package/dist/api/routes/recordsExport.js +67 -0
- package/dist/api/routes/recordsExport.js.map +1 -0
- package/dist/api/routes/recordsImport.d.ts +4 -0
- package/dist/api/routes/recordsImport.js +100 -0
- package/dist/api/routes/recordsImport.js.map +1 -0
- package/dist/api/routes/searchCompanies.d.ts +17 -0
- package/dist/api/routes/searchCompanies.js +72 -0
- package/dist/api/routes/searchCompanies.js.map +1 -0
- package/dist/api/routes/subrecordsImport.d.ts +4 -0
- package/dist/api/routes/subrecordsImport.js +94 -0
- package/dist/api/routes/subrecordsImport.js.map +1 -0
- package/dist/api/routes/updateRecord.d.ts +13 -0
- package/dist/api/routes/updateRecord.js +55 -0
- package/dist/api/routes/updateRecord.js.map +1 -0
- package/dist/api/routes/uploadFile.d.ts +3 -0
- package/dist/api/routes/uploadFile.js +13 -0
- package/dist/api/routes/uploadFile.js.map +1 -0
- package/dist/api/routes/uploadFileFromBetterRichTextInput.d.ts +3 -0
- package/dist/api/routes/uploadFileFromBetterRichTextInput.js +33 -0
- package/dist/api/routes/uploadFileFromBetterRichTextInput.js.map +1 -0
- package/dist/api/uploadFile.d.ts +4 -0
- package/dist/api/uploadFile.js +209 -0
- package/dist/api/uploadFile.js.map +1 -0
- package/dist/api/values.d.ts +6 -0
- package/dist/api/values.js +88 -0
- package/dist/api/values.js.map +1 -0
- package/dist/api.d.ts +8 -27
- package/dist/api.js +80 -1387
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.strict.tsbuildinfo +1 -0
- package/dist/types/config.d.ts +15 -5
- package/dist/types/config.js +4 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/database.d.ts +1 -0
- package/dist/types/database.js +3 -0
- package/dist/types/database.js.map +1 -0
- package/dist/utils/dbManager.d.ts +32 -7
- package/dist/utils/dbManager.js +59 -7
- package/dist/utils/dbManager.js.map +1 -1
- package/package.json +12 -3
- package/src/api/authorization.ts +129 -0
- package/src/api/config.ts +152 -0
- package/src/api/filterUser.ts +28 -0
- package/src/api/importRecords.ts +16 -0
- package/src/api/queries.ts +194 -0
- package/src/api/request.ts +31 -0
- package/src/api/routes/adjacentRecordId.ts +20 -0
- package/src/api/routes/config.ts +15 -0
- package/src/api/routes/createRecord.ts +131 -0
- package/src/api/routes/customAction.ts +60 -0
- package/src/api/routes/customComponent.ts +44 -0
- package/src/api/routes/customQuery.ts +53 -0
- package/src/api/routes/deleteRecord.ts +59 -0
- package/src/api/routes/deleteRecordFromPanel.ts +58 -0
- package/src/api/routes/deleteRecords.ts +28 -0
- package/src/api/routes/duplicateRecord.ts +73 -0
- package/src/api/routes/foreignSelectRecords.ts +80 -0
- package/src/api/routes/notificationBadges.ts +47 -0
- package/src/api/routes/record.ts +48 -0
- package/src/api/routes/records.ts +91 -0
- package/src/api/routes/recordsExport.ts +92 -0
- package/src/api/routes/recordsImport.ts +115 -0
- package/src/api/routes/searchCompanies.ts +96 -0
- package/src/api/routes/subrecordsImport.ts +114 -0
- package/src/api/routes/updateRecord.ts +74 -0
- package/src/api/routes/uploadFile.ts +16 -0
- package/src/api/routes/uploadFileFromBetterRichTextInput.ts +41 -0
- package/src/api/uploadFile.ts +249 -0
- package/src/api/values.ts +96 -0
- package/src/api.ts +94 -1580
- package/src/index.ts +42 -25
- package/src/types/config.ts +15 -5
- package/src/types/database.ts +2 -0
- package/src/utils/dbManager.ts +99 -14
- package/tests/api-helpers.test.ts +59 -0
- package/tests/dbManager.test.ts +16 -1
- package/tests/helpers/fakeDb.ts +9 -7
- package/tests/integration/authorizationBoundary.integration.test.ts +57 -0
- package/tests/integration/configEnrichment.integration.test.ts +79 -0
- package/tests/integration/customComponentBoundary.integration.test.ts +58 -0
- package/tests/integration/dbManager.integration.test.ts +105 -3
- package/tests/integration/importParserChild.cjs +55 -0
- package/tests/integration/indexRuntime.integration.test.ts +22 -0
- package/tests/integration/indexRuntimeChild.cjs +110 -0
- package/tests/integration/queries.integration.test.ts +88 -0
- package/tests/integration/requestConfig.integration.test.ts +118 -0
- package/tests/integration/routes.authorization.integration.test.ts +1 -0
- package/tests/integration/routes.config.integration.test.ts +39 -1
- package/tests/integration/routes.crud.integration.test.ts +233 -1
- package/tests/integration/routes.custom.integration.test.ts +119 -0
- package/tests/integration/routes.foreign.integration.test.ts +3 -2
- package/tests/integration/routes.importExport.integration.test.ts +308 -6
- package/tests/integration/routes.integration.test.ts +4 -0
- package/tests/integration/routes.records.integration.test.ts +82 -6
- package/tests/integration/routes.upload.integration.test.ts +123 -0
- package/tests/integration/userFilters.integration.test.ts +225 -0
- package/tsconfig.json +1 -1
- package/tsconfig.strict.json +45 -0
- package/dist/front/ckeditor5-build/src/ckeditor.d.ts +0 -43
- package/dist/front/ckeditor5-build/src/ckeditor.js +0 -130
- package/dist/front/ckeditor5-build/src/ckeditor.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { quoteIdentifier } from "../utils/dbManager";
|
|
2
|
+
import type dbManager from "../utils/dbManager";
|
|
3
|
+
import type { DatabaseRow } from "../utils/dbManager";
|
|
4
|
+
import type { BaseTypeColumn, Filter, StandardColumn } from "../types/config";
|
|
5
|
+
|
|
6
|
+
type Condition = string | Filter[];
|
|
7
|
+
type RecordQueryParams = {
|
|
8
|
+
table: string;
|
|
9
|
+
page: string | number;
|
|
10
|
+
limit: string | number;
|
|
11
|
+
allRecords?: boolean;
|
|
12
|
+
filters?: Condition | null;
|
|
13
|
+
query?: string;
|
|
14
|
+
columns?: (Pick<BaseTypeColumn, "searchable" | "searchableName"> & StandardColumn)[];
|
|
15
|
+
customQuery?: string;
|
|
16
|
+
selectExpression?: string;
|
|
17
|
+
join?: string;
|
|
18
|
+
groupBy?: string;
|
|
19
|
+
having?: string;
|
|
20
|
+
orderBy?: string | null;
|
|
21
|
+
primaryId?: string;
|
|
22
|
+
};
|
|
23
|
+
type RecordQueryResult = {
|
|
24
|
+
rows: DatabaseRow[];
|
|
25
|
+
queryString: string;
|
|
26
|
+
input: { query: string; params: RecordQueryParams };
|
|
27
|
+
count: NonNullable<DatabaseRow[string]> | undefined;
|
|
28
|
+
error?: never;
|
|
29
|
+
} | {
|
|
30
|
+
error: Error;
|
|
31
|
+
rows?: never;
|
|
32
|
+
queryString?: never;
|
|
33
|
+
input?: never;
|
|
34
|
+
count?: never;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
#PORTING-EDIT
|
|
39
|
+
Reject absent rows, mutation headers, and nested result sets where record queries require row arrays.
|
|
40
|
+
*/
|
|
41
|
+
function readRecordRows(rows: Awaited<ReturnType<dbManager["query"]>>["rows"]): DatabaseRow[] {
|
|
42
|
+
if (!Array.isArray(rows) || !rows.every((row): row is DatabaseRow => !Array.isArray(row))) {
|
|
43
|
+
throw new TypeError("Invalid record query rows");
|
|
44
|
+
}
|
|
45
|
+
return rows;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function sqlAnd(conditions: (Condition | null | undefined)[]) {
|
|
49
|
+
const existingConditions: Condition[] = [];
|
|
50
|
+
for (const condition of conditions) {
|
|
51
|
+
if (condition && condition.length > 0) {
|
|
52
|
+
existingConditions.push(condition);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (existingConditions.length <= 1) {
|
|
56
|
+
return existingConditions[0] || null;
|
|
57
|
+
} else {
|
|
58
|
+
let result = "";
|
|
59
|
+
for (let i = 0; i < existingConditions.length; i++) {
|
|
60
|
+
if (i > 0) {
|
|
61
|
+
result += " AND ";
|
|
62
|
+
}
|
|
63
|
+
result += "(" + existingConditions[i] + ")";
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Collect every SQL template declared anywhere in the (role-filtered) config:
|
|
70
|
+
// panel `query`, `autocompleteListQuery`, etc. customQuery may only run a
|
|
71
|
+
// template that already exists in the config the caller is allowed to see —
|
|
72
|
+
// so it can never be turned into an arbitrary read/write primitive.
|
|
73
|
+
export function collectAllowedQueries(node: unknown, acc = new Set<string>()): Set<string> {
|
|
74
|
+
if (!node || typeof node !== "object") return acc;
|
|
75
|
+
if (Array.isArray(node)) {
|
|
76
|
+
for (const item of node) collectAllowedQueries(item, acc);
|
|
77
|
+
return acc;
|
|
78
|
+
}
|
|
79
|
+
for (const key of Object.keys(node)) {
|
|
80
|
+
const value: unknown = Reflect.get(node, key);
|
|
81
|
+
if (typeof value === "string" && (key === "query" || /Query$/.test(key))) {
|
|
82
|
+
acc.add(value.trim());
|
|
83
|
+
} else if (value && typeof value === "object") {
|
|
84
|
+
collectAllowedQueries(value, acc);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return acc;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default (db: Pick<dbManager, "query" | "dbType">) => {
|
|
91
|
+
async function getRecords(params: RecordQueryParams, computeCount = false): Promise<RecordQueryResult> {
|
|
92
|
+
let queryString = "";
|
|
93
|
+
if (params.filters) {
|
|
94
|
+
queryString = " WHERE (" + params.filters + ")";
|
|
95
|
+
}
|
|
96
|
+
if (params.query) {
|
|
97
|
+
const columns = params.columns ?? [];
|
|
98
|
+
let isNewCondition = true;
|
|
99
|
+
for (let i in columns) {
|
|
100
|
+
if (
|
|
101
|
+
columns[i].searchable !== false &&
|
|
102
|
+
(columns[i].searchableName || columns[i].name)
|
|
103
|
+
) {
|
|
104
|
+
if (queryString.length == 0) {
|
|
105
|
+
queryString += " WHERE (";
|
|
106
|
+
} else if (isNewCondition) {
|
|
107
|
+
queryString += " AND (";
|
|
108
|
+
} else {
|
|
109
|
+
queryString += " OR ";
|
|
110
|
+
}
|
|
111
|
+
if (columns[i].searchableName) {
|
|
112
|
+
// on ne doit pas ajouter de backticks sur le searchableName car celui-ci peut être une fonction
|
|
113
|
+
// exemple : searchableName = "CONCAT_WS(' ', firstname, lastname)"
|
|
114
|
+
queryString += columns[i].searchableName;
|
|
115
|
+
} else if (columns[i].name) {
|
|
116
|
+
queryString += columns[i].name
|
|
117
|
+
.split(".")
|
|
118
|
+
.map((x) => quoteIdentifier(x, db.dbType ?? ""))
|
|
119
|
+
.join(".");
|
|
120
|
+
}
|
|
121
|
+
// SECURITY: escape the user query per dialect — PostgreSQL does not
|
|
122
|
+
// treat backslashes as escapes inside standard strings, so a `\'`
|
|
123
|
+
// would terminate the literal early.
|
|
124
|
+
const searchEscaped =
|
|
125
|
+
db.dbType === "POSTGRESQL"
|
|
126
|
+
? params.query.replace(/'/g, "''")
|
|
127
|
+
: params.query.replace(/'/g, "\\'");
|
|
128
|
+
queryString += " LIKE '%" + searchEscaped + "%'";
|
|
129
|
+
isNewCondition = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
queryString += ")";
|
|
133
|
+
}
|
|
134
|
+
const limit = params.allRecords ? 999999 : parseInt(String(params.limit));
|
|
135
|
+
const query =
|
|
136
|
+
params.customQuery ||
|
|
137
|
+
`SELECT
|
|
138
|
+
${params.selectExpression || "*"}
|
|
139
|
+
FROM ${params.table}
|
|
140
|
+
${params.join || ""}
|
|
141
|
+
${queryString}
|
|
142
|
+
${params.groupBy ? "GROUP BY " + params.groupBy : ""}
|
|
143
|
+
${params.having ? " HAVING " + params.having : ""}
|
|
144
|
+
${params.orderBy || params.primaryId ? " ORDER BY " + (params.orderBy || `${params.table}.${params.primaryId}`) : ""}`;
|
|
145
|
+
const { err, rows } = await db.query(
|
|
146
|
+
`${query} LIMIT ${limit} OFFSET ${Math.max((Number(params.page) - 1) * limit, 0)}`,
|
|
147
|
+
);
|
|
148
|
+
if (err) {
|
|
149
|
+
console.error(err);
|
|
150
|
+
return {
|
|
151
|
+
error: err,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const records = readRecordRows(rows);
|
|
155
|
+
let count: NonNullable<DatabaseRow[string]> | undefined = undefined;
|
|
156
|
+
if (computeCount) {
|
|
157
|
+
const { err: countErr, rows: countRows } = await db.query(
|
|
158
|
+
`SELECT COUNT(*) AS count FROM (${query}) s`,
|
|
159
|
+
);
|
|
160
|
+
if (countErr) {
|
|
161
|
+
const { rows: allRows } = await db.query(query);
|
|
162
|
+
count = readRecordRows(allRows).length;
|
|
163
|
+
} else {
|
|
164
|
+
count = countRows === undefined ? 0 : readRecordRows(countRows)[0]?.count || 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
rows: records,
|
|
170
|
+
queryString,
|
|
171
|
+
input: {
|
|
172
|
+
query,
|
|
173
|
+
params,
|
|
174
|
+
},
|
|
175
|
+
count,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function findRecordByPosition(params: Omit<RecordQueryParams, "page" | "limit"> & {
|
|
180
|
+
position: string | number;
|
|
181
|
+
offset: string | number;
|
|
182
|
+
page?: string | number;
|
|
183
|
+
limit?: string | number;
|
|
184
|
+
}) {
|
|
185
|
+
return await getRecords(
|
|
186
|
+
Object.assign(params, {
|
|
187
|
+
page: parseInt(String(params.position)) + parseInt(String(params.offset)),
|
|
188
|
+
limit: 1,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { getRecords, findRecordByPosition };
|
|
194
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Column, Config, CreateConfig, ForeignDatalistColumn, StandardColumn,
|
|
3
|
+
Subview, TablePanel, TableView, View, ViewPanel,
|
|
4
|
+
} from "../types/config";
|
|
5
|
+
|
|
6
|
+
export type { RequestHandler } from "express";
|
|
7
|
+
|
|
8
|
+
type Identified = { __id__?: string };
|
|
9
|
+
type RequestColumn = Column & StandardColumn & Identified
|
|
10
|
+
& Partial<Omit<ForeignDatalistColumn, "dataType">>;
|
|
11
|
+
type RequestPanel = ViewPanel & Identified
|
|
12
|
+
& Partial<Omit<TablePanel, "type" | "createConfig">> & {
|
|
13
|
+
columns?: RequestColumn[];
|
|
14
|
+
createConfig?: CreateConfig & { tableName?: string; columns: RequestColumn[] };
|
|
15
|
+
};
|
|
16
|
+
type RequestView = View & Identified
|
|
17
|
+
& Partial<Omit<TableView, "type" | "columns" | "recordViewPanels" | "subviews">> & {
|
|
18
|
+
columns?: RequestColumn[];
|
|
19
|
+
__hiddenColumns?: string[];
|
|
20
|
+
recordViewPanels?: RequestPanel[];
|
|
21
|
+
subviews?: (Subview & Identified)[];
|
|
22
|
+
};
|
|
23
|
+
type RequestConfig = Omit<Config, "views"> & { views: RequestView[] };
|
|
24
|
+
|
|
25
|
+
declare global {
|
|
26
|
+
namespace Express {
|
|
27
|
+
interface Request {
|
|
28
|
+
config?: RequestConfig;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import type createRecordQueries from "../queries";
|
|
3
|
+
import type { DatabaseRow } from "../../utils/dbManager";
|
|
4
|
+
|
|
5
|
+
type FindRecordByPosition = ReturnType<typeof createRecordQueries>["findRecordByPosition"];
|
|
6
|
+
type RequestBody = Parameters<FindRecordByPosition>[0];
|
|
7
|
+
type ResponseBody = Error | { recordId: DatabaseRow[string] | undefined };
|
|
8
|
+
|
|
9
|
+
export default (findRecordByPosition: FindRecordByPosition): RequestHandler<Record<string, string>, ResponseBody, RequestBody> =>
|
|
10
|
+
async function (req, res, next) {
|
|
11
|
+
const { rows, error } = await findRecordByPosition(req.body);
|
|
12
|
+
if (error) {
|
|
13
|
+
return res.status(500).send(error);
|
|
14
|
+
}
|
|
15
|
+
// JavaScript coerces an omitted primaryId to the property key "undefined".
|
|
16
|
+
const recordId = rows[0] ? rows[0][req.body.primaryId as string] : null;
|
|
17
|
+
res.send({
|
|
18
|
+
recordId: recordId,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import * as serializeJs from "serialize-javascript";
|
|
3
|
+
|
|
4
|
+
export default (): RequestHandler =>
|
|
5
|
+
async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const { mysql, auth, sessionSecret, ...safeConfig } = req.config!;
|
|
8
|
+
res.send({
|
|
9
|
+
config: serializeJs(safeConfig),
|
|
10
|
+
});
|
|
11
|
+
} catch (e) {
|
|
12
|
+
console.error(e);
|
|
13
|
+
res.sendStatus(500);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import type DbManager from "../../utils/dbManager";
|
|
3
|
+
import type { DatabaseRow } from "../../utils/dbManager";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import { v4 as uuidv4 } from "uuid";
|
|
6
|
+
import { quoteIdentifier } from "../../utils/dbManager";
|
|
7
|
+
import { areSafeColumnNames, touchesHiddenColumn } from "../authorization";
|
|
8
|
+
|
|
9
|
+
type RequestBody = {
|
|
10
|
+
viewIndex: string | number;
|
|
11
|
+
panelIndex?: string | number | null;
|
|
12
|
+
recordId?: DatabaseRow[string];
|
|
13
|
+
data: Partial<DatabaseRow>;
|
|
14
|
+
};
|
|
15
|
+
type ResponseBody =
|
|
16
|
+
| { id: number | undefined }
|
|
17
|
+
| { err: unknown; message: string }
|
|
18
|
+
| { error: string };
|
|
19
|
+
|
|
20
|
+
export default (db: DbManager): RequestHandler<
|
|
21
|
+
Record<string, string>,
|
|
22
|
+
ResponseBody,
|
|
23
|
+
RequestBody
|
|
24
|
+
> =>
|
|
25
|
+
async function (req, res, next) {
|
|
26
|
+
const view = req.config!.views[req.body.viewIndex as number];
|
|
27
|
+
// panelIndex arrives as a string from the front-end but a numeric caller
|
|
28
|
+
// may send 0 — only null/undefined mean "no panel".
|
|
29
|
+
const hasPanel =
|
|
30
|
+
req.body.panelIndex !== undefined && req.body.panelIndex !== null;
|
|
31
|
+
const panel = hasPanel
|
|
32
|
+
? view.recordViewPanels![req.body.panelIndex as number]
|
|
33
|
+
: null;
|
|
34
|
+
if (!hasPanel && touchesHiddenColumn(view, req.body.data)) {
|
|
35
|
+
return res.sendStatus(403);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
for (let key in req.body.data) {
|
|
39
|
+
if (
|
|
40
|
+
req.body.data[key] == "NULL" ||
|
|
41
|
+
req.body.data[key] === "Invalid date"
|
|
42
|
+
) {
|
|
43
|
+
req.body.data[key] = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (panel) {
|
|
47
|
+
req.body.data[panel.createConfig!.recordIdKey] = req.body.recordId;
|
|
48
|
+
}
|
|
49
|
+
const primaryColumn = (view || panel)?.columns?.find(
|
|
50
|
+
(x) => x.name === (view || panel).primaryId,
|
|
51
|
+
);
|
|
52
|
+
if (primaryColumn?.dataType == "uuid") {
|
|
53
|
+
req.body.data[String(primaryColumn.name)] = uuidv4();
|
|
54
|
+
}
|
|
55
|
+
const createPayload = JSON.parse(
|
|
56
|
+
JSON.stringify(req.body.data),
|
|
57
|
+
) as Partial<DatabaseRow>;
|
|
58
|
+
const columns = panel ? panel.createConfig!.columns : view.columns;
|
|
59
|
+
for (let key in createPayload) {
|
|
60
|
+
if (
|
|
61
|
+
columns?.find((x) => x.name === key)?.includedInCreatePayload ===
|
|
62
|
+
false
|
|
63
|
+
) {
|
|
64
|
+
delete createPayload[key];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!areSafeColumnNames(createPayload)) {
|
|
68
|
+
return res.status(400).send({ error: "Invalid column name" });
|
|
69
|
+
}
|
|
70
|
+
const queryExpression = Object.keys(createPayload)
|
|
71
|
+
.map((x) => quoteIdentifier(x, db.dbType!))
|
|
72
|
+
.join(", ");
|
|
73
|
+
const queryPlaceholders = Object.keys(createPayload)
|
|
74
|
+
.map((x) => "?")
|
|
75
|
+
.join(", ");
|
|
76
|
+
const queryParameters = Object.keys(createPayload).map(
|
|
77
|
+
(x) => createPayload[x],
|
|
78
|
+
);
|
|
79
|
+
const { err, rows: r } = await db.query(
|
|
80
|
+
`INSERT INTO ?? (${queryExpression}) VALUES(${queryPlaceholders})`,
|
|
81
|
+
[panel ? panel.tableName : view.tableName, ...queryParameters],
|
|
82
|
+
);
|
|
83
|
+
if (err) {
|
|
84
|
+
console.error(err);
|
|
85
|
+
return res.status(500).send({
|
|
86
|
+
err,
|
|
87
|
+
message:
|
|
88
|
+
"code" in err && err.code === "ER_DUP_ENTRY"
|
|
89
|
+
? "Cet élément existe déjà."
|
|
90
|
+
: "Une erreur est survenue.",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (panel ? panel.createCallback : view.createCallback) {
|
|
94
|
+
try {
|
|
95
|
+
await axios.post(
|
|
96
|
+
panel ? panel.createCallback! : view.createCallback!,
|
|
97
|
+
{
|
|
98
|
+
id: Array.isArray(r) ? undefined : r!.insertId,
|
|
99
|
+
data: req.body.data,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
} catch (e) {}
|
|
103
|
+
}
|
|
104
|
+
if (panel ? panel.createCallbackFunction : view.createCallbackFunction) {
|
|
105
|
+
// The insert is already committed: a failing callback must not turn
|
|
106
|
+
// into a 500 that invites a client retry (which would duplicate the
|
|
107
|
+
// row). Log and answer 200.
|
|
108
|
+
try {
|
|
109
|
+
await (
|
|
110
|
+
panel
|
|
111
|
+
? panel.createCallbackFunction!
|
|
112
|
+
: view.createCallbackFunction!
|
|
113
|
+
)({
|
|
114
|
+
id: (Array.isArray(r) ? undefined : r!.insertId) as number, // TODO https://github.com/galadrimteam/galadmin/issues/45: PostgreSQL supplies undefined here.
|
|
115
|
+
data: req.body.data,
|
|
116
|
+
});
|
|
117
|
+
} catch (callbackErr) {
|
|
118
|
+
console.error(callbackErr);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
res.send({
|
|
122
|
+
id: Array.isArray(r) ? undefined : r!.insertId,
|
|
123
|
+
});
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(err);
|
|
126
|
+
res.status(500).send({
|
|
127
|
+
err,
|
|
128
|
+
message: "Une erreur est survenue.",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import type DbManager from "../../utils/dbManager";
|
|
3
|
+
import type { DatabaseRow } from "../../utils/dbManager";
|
|
4
|
+
|
|
5
|
+
type RequestBody = {
|
|
6
|
+
viewIndex: string | number;
|
|
7
|
+
actionIndex: string | number;
|
|
8
|
+
selectedRecordIds: DatabaseRow[string][];
|
|
9
|
+
};
|
|
10
|
+
type ResponseBody =
|
|
11
|
+
| { success: true; message: string | undefined }
|
|
12
|
+
| { success: false; error: unknown };
|
|
13
|
+
type ActionUser = { userId?: DatabaseRow[string] };
|
|
14
|
+
type QueryRows = NonNullable<Awaited<ReturnType<DbManager["query"]>>["rows"]>;
|
|
15
|
+
type IndexedRows = {
|
|
16
|
+
[index: number]: Extract<QueryRows, object[]>[number] | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default (db: DbManager): RequestHandler<
|
|
20
|
+
Record<string, string>,
|
|
21
|
+
ResponseBody,
|
|
22
|
+
RequestBody
|
|
23
|
+
> =>
|
|
24
|
+
async function (req, res, next) {
|
|
25
|
+
const view = req.config!.views[req.body.viewIndex as number];
|
|
26
|
+
const action = view.customActions![req.body.actionIndex as number];
|
|
27
|
+
for (const recordId of req.body.selectedRecordIds) {
|
|
28
|
+
try {
|
|
29
|
+
const { rows } = await db.query(
|
|
30
|
+
"SELECT " +
|
|
31
|
+
(view.selectExpression || "*") +
|
|
32
|
+
" FROM ?? " +
|
|
33
|
+
(view.join || "") +
|
|
34
|
+
" WHERE ?? = ?",
|
|
35
|
+
[view.tableName, view.tableName + "." + view.primaryId, recordId],
|
|
36
|
+
);
|
|
37
|
+
const record = (rows as IndexedRows)[0];
|
|
38
|
+
const actionResult = await action.action(
|
|
39
|
+
record,
|
|
40
|
+
(req.session!.user as ActionUser).userId,
|
|
41
|
+
);
|
|
42
|
+
if (actionResult) {
|
|
43
|
+
return res.send({
|
|
44
|
+
success: true,
|
|
45
|
+
message: actionResult,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error(err);
|
|
50
|
+
return res.send({
|
|
51
|
+
success: false,
|
|
52
|
+
error: err,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
res.send({
|
|
57
|
+
success: true,
|
|
58
|
+
message: action.successMessage,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as rollup from "rollup";
|
|
5
|
+
import babel from "@rollup/plugin-babel";
|
|
6
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
7
|
+
|
|
8
|
+
export default (__dirname: string): RequestHandler<Record<string, string>, string> =>
|
|
9
|
+
async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const pkg: unknown = JSON.parse(
|
|
12
|
+
fs.readFileSync(
|
|
13
|
+
path.join(__dirname, "../src/front/package.json"),
|
|
14
|
+
"utf-8",
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
const external = Object.keys(
|
|
18
|
+
(pkg as { dependencies?: unknown }).dependencies || {},
|
|
19
|
+
);
|
|
20
|
+
const bundle = await rollup.rollup({
|
|
21
|
+
input: path.join(
|
|
22
|
+
__dirname,
|
|
23
|
+
"../../",
|
|
24
|
+
req.query.componentPath as string,
|
|
25
|
+
),
|
|
26
|
+
plugins: [
|
|
27
|
+
resolve(),
|
|
28
|
+
babel({
|
|
29
|
+
configFile: path.join(__dirname, "../babel.config"),
|
|
30
|
+
babelHelpers: "bundled",
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
external,
|
|
34
|
+
});
|
|
35
|
+
const { output } = await bundle.generate({
|
|
36
|
+
format: "cjs",
|
|
37
|
+
exports: "auto",
|
|
38
|
+
});
|
|
39
|
+
res.send(output[0].code);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(err);
|
|
42
|
+
res.sendStatus(500);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import { collectAllowedQueries } from "../queries";
|
|
3
|
+
import type DbManager from "../../utils/dbManager";
|
|
4
|
+
import type { DatabaseRow } from "../../utils/dbManager";
|
|
5
|
+
|
|
6
|
+
type QueryRows = NonNullable<Awaited<ReturnType<DbManager["query"]>>["rows"]>;
|
|
7
|
+
type IterableRows = Extract<QueryRows, object[]>;
|
|
8
|
+
type ResponseBody =
|
|
9
|
+
| Awaited<ReturnType<DbManager["query"]>>["rows"]
|
|
10
|
+
| Error
|
|
11
|
+
| { error: string };
|
|
12
|
+
|
|
13
|
+
export default (db: DbManager): RequestHandler<
|
|
14
|
+
Record<string, string>,
|
|
15
|
+
ResponseBody
|
|
16
|
+
> =>
|
|
17
|
+
async function (req, res, next) {
|
|
18
|
+
const template =
|
|
19
|
+
typeof req.query.query === "string" ? req.query.query : "";
|
|
20
|
+
// SECURITY: only run whitelisted, read-only templates, and bind the record id
|
|
21
|
+
// as a parameter instead of substituting it into the SQL string.
|
|
22
|
+
const allowed = collectAllowedQueries(req.config!.views);
|
|
23
|
+
if (!allowed.has(template.trim())) {
|
|
24
|
+
return res.status(403).send({ error: "Query not allowed" });
|
|
25
|
+
}
|
|
26
|
+
if (!/^\s*\(?\s*(select|with)\b/i.test(template) || /;/.test(template)) {
|
|
27
|
+
return res
|
|
28
|
+
.status(400)
|
|
29
|
+
.send({ error: "Only read-only queries are allowed" });
|
|
30
|
+
}
|
|
31
|
+
const placeholderCount = (template.match(/\?/g) || []).length;
|
|
32
|
+
const params = new Array<typeof req.query.primaryValue>(placeholderCount).fill(
|
|
33
|
+
req.query.primaryValue,
|
|
34
|
+
);
|
|
35
|
+
const { err, rows } = await db.query(template, params);
|
|
36
|
+
if (err) {
|
|
37
|
+
return res.status(500).send(err);
|
|
38
|
+
}
|
|
39
|
+
if (req.query.viewIndex && req.query.panelIndex) {
|
|
40
|
+
const panel =
|
|
41
|
+
req.config!.views[req.query.viewIndex as `${number}`].recordViewPanels![
|
|
42
|
+
req.query.panelIndex as `${number}`
|
|
43
|
+
];
|
|
44
|
+
if (panel.formatValue) {
|
|
45
|
+
for (const row of rows as IterableRows) {
|
|
46
|
+
for (const key in panel.formatValue) {
|
|
47
|
+
(row as DatabaseRow)[key] = await panel.formatValue[key](row);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
res.send(rows);
|
|
53
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import type DbManager from "../../utils/dbManager";
|
|
3
|
+
import type { DatabaseRow } from "../../utils/dbManager";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
|
|
6
|
+
type RequestBody = {
|
|
7
|
+
viewIndex: string | number;
|
|
8
|
+
primaryValue: DatabaseRow[string];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default (db: DbManager): RequestHandler<
|
|
12
|
+
Record<string, string>,
|
|
13
|
+
string | Error,
|
|
14
|
+
RequestBody
|
|
15
|
+
> =>
|
|
16
|
+
async function (req, res) {
|
|
17
|
+
const view = req.config!.views[req.body.viewIndex as number];
|
|
18
|
+
const { rows, err: err2 } = await db.query(
|
|
19
|
+
"SELECT " +
|
|
20
|
+
(view.selectExpression || "*") +
|
|
21
|
+
" FROM ?? " +
|
|
22
|
+
(view.join || "") +
|
|
23
|
+
" WHERE ?? = ?",
|
|
24
|
+
[
|
|
25
|
+
view.tableName,
|
|
26
|
+
view.tableName + "." + view.primaryId,
|
|
27
|
+
req.body.primaryValue,
|
|
28
|
+
],
|
|
29
|
+
);
|
|
30
|
+
if (err2) {
|
|
31
|
+
return res.status(500).send(err2);
|
|
32
|
+
}
|
|
33
|
+
const deletedRecord = (rows as DatabaseRow[])[0];
|
|
34
|
+
if (!deletedRecord) {
|
|
35
|
+
return res.status(404).send("Record not found");
|
|
36
|
+
}
|
|
37
|
+
const { err } = await db.query("DELETE FROM ?? WHERE ?? = ?", [
|
|
38
|
+
view.tableName,
|
|
39
|
+
view.primaryId,
|
|
40
|
+
req.body.primaryValue,
|
|
41
|
+
]);
|
|
42
|
+
if (err) {
|
|
43
|
+
return res.status(500).send(err);
|
|
44
|
+
}
|
|
45
|
+
if (view.deleteCallback) {
|
|
46
|
+
try {
|
|
47
|
+
await axios.post(view.deleteCallback, {
|
|
48
|
+
id: req.body.primaryValue,
|
|
49
|
+
});
|
|
50
|
+
} catch (e) {}
|
|
51
|
+
}
|
|
52
|
+
if (view.deleteCallbackFunction) {
|
|
53
|
+
await view.deleteCallbackFunction({
|
|
54
|
+
id: req.body.primaryValue as number, // TODO https://github.com/galadrimteam/galadmin/issues/45: string and UUID keys are also supplied here.
|
|
55
|
+
deletedRecord,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
res.sendStatus(200);
|
|
59
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RequestHandler } from "../request";
|
|
2
|
+
import type DbManager from "../../utils/dbManager";
|
|
3
|
+
import type { DatabaseRow } from "../../utils/dbManager";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
|
|
6
|
+
type RequestBody = {
|
|
7
|
+
viewIndex: string | number;
|
|
8
|
+
panelId: string;
|
|
9
|
+
primaryValue: DatabaseRow[string];
|
|
10
|
+
};
|
|
11
|
+
type DeleteCallbackPayload = {
|
|
12
|
+
id: number;
|
|
13
|
+
deletedRecord: DatabaseRow | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default (db: DbManager): RequestHandler<
|
|
17
|
+
Record<string, string>,
|
|
18
|
+
string | Error,
|
|
19
|
+
RequestBody
|
|
20
|
+
> =>
|
|
21
|
+
async function (req, res) {
|
|
22
|
+
const view = req.config!.views[req.body.viewIndex as number];
|
|
23
|
+
const panel = view.recordViewPanels!.find(
|
|
24
|
+
(x) => x.__id__ === req.body.panelId,
|
|
25
|
+
)!;
|
|
26
|
+
const { rows, err: err2 } = await db.query(
|
|
27
|
+
"SELECT * FROM ?? WHERE ?? = ?",
|
|
28
|
+
[panel.tableName, panel.primaryId, req.body.primaryValue],
|
|
29
|
+
);
|
|
30
|
+
if (err2) {
|
|
31
|
+
return res.status(500).send(err2);
|
|
32
|
+
}
|
|
33
|
+
const deletedRecord = (rows as (DatabaseRow | undefined)[])[0];
|
|
34
|
+
const { err } = await db.query("DELETE FROM ?? WHERE ?? = ?", [
|
|
35
|
+
panel.tableName,
|
|
36
|
+
panel.primaryId,
|
|
37
|
+
req.body.primaryValue,
|
|
38
|
+
]);
|
|
39
|
+
if (err) {
|
|
40
|
+
return res.status(500).send(err);
|
|
41
|
+
}
|
|
42
|
+
if (panel.deleteCallback) {
|
|
43
|
+
try {
|
|
44
|
+
await axios.post(panel.deleteCallback, {
|
|
45
|
+
id: req.body.primaryValue,
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {}
|
|
48
|
+
}
|
|
49
|
+
if (panel.deleteCallbackFunction) {
|
|
50
|
+
await (panel.deleteCallbackFunction as (
|
|
51
|
+
payload: DeleteCallbackPayload,
|
|
52
|
+
) => void)({
|
|
53
|
+
id: req.body.primaryValue as number, // TODO https://github.com/galadrimteam/galadmin/issues/45: string and UUID keys are also supplied here.
|
|
54
|
+
deletedRecord,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
res.sendStatus(200);
|
|
58
|
+
};
|