@heyhru/app-dms-server 0.3.7 → 0.4.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/dist/index.js +69 -391
- package/package.json +12 -8
package/dist/index.js
CHANGED
|
@@ -66,7 +66,12 @@ var PERMISSIONS = {
|
|
|
66
66
|
"POST /approvals/reject": { auth: true, roles: ["admin", "maintainer"] },
|
|
67
67
|
"POST /approvals/execute": { auth: true, roles: ["admin", "maintainer"] },
|
|
68
68
|
// Audit
|
|
69
|
-
"POST /logs/list": { auth: true, roles: ["admin", "maintainer"] }
|
|
69
|
+
"POST /logs/list": { auth: true, roles: ["admin", "maintainer"] },
|
|
70
|
+
// Saved SQLs
|
|
71
|
+
"POST /saved-sqls/list": { auth: true },
|
|
72
|
+
"POST /saved-sqls/create": { auth: true },
|
|
73
|
+
"POST /saved-sqls/update": { auth: true },
|
|
74
|
+
"POST /saved-sqls/delete": { auth: true }
|
|
70
75
|
};
|
|
71
76
|
async function authHook(req, reply) {
|
|
72
77
|
const key = `${req.method} ${req.routeOptions.url}`;
|
|
@@ -145,177 +150,15 @@ function userController(app) {
|
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
// src/datasources/datasources.service.ts
|
|
148
|
-
import {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
var
|
|
157
|
-
SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
|
|
158
|
-
FROM data_sources
|
|
159
|
-
ORDER BY created_at DESC`;
|
|
160
|
-
var FIND_BY_ID = `
|
|
161
|
-
SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
|
|
162
|
-
FROM data_sources
|
|
163
|
-
WHERE id = ?`;
|
|
164
|
-
var FIND_WITH_PASSWORD = `
|
|
165
|
-
SELECT *
|
|
166
|
-
FROM data_sources
|
|
167
|
-
WHERE id = ?`;
|
|
168
|
-
var CREATE = `
|
|
169
|
-
INSERT INTO data_sources (name, type, host, port, database, username, password_encrypted, ssl, pool_min, pool_max, created_by)
|
|
170
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
171
|
-
RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`;
|
|
172
|
-
var UPDATE_FIELDS = ["name", "type", "host", "port", "database", "username", "ssl", "pool_min", "pool_max"];
|
|
173
|
-
var DELETE = `
|
|
174
|
-
DELETE FROM data_sources
|
|
175
|
-
WHERE id = ?`;
|
|
176
|
-
|
|
177
|
-
// src/datasources/datasources.model.ts
|
|
178
|
-
function listDataSources() {
|
|
179
|
-
return getPgDb().query(LIST);
|
|
180
|
-
}
|
|
181
|
-
function getDataSourceById(id) {
|
|
182
|
-
return getPgDb().queryOne(FIND_BY_ID, [id]);
|
|
183
|
-
}
|
|
184
|
-
function getDataSourceRow(id) {
|
|
185
|
-
return getPgDb().queryOne(FIND_WITH_PASSWORD, [id]);
|
|
186
|
-
}
|
|
187
|
-
function insertDataSource(data, encryptedPassword, createdBy) {
|
|
188
|
-
return getPgDb().queryOne(CREATE, [
|
|
189
|
-
data.name,
|
|
190
|
-
data.type,
|
|
191
|
-
data.host,
|
|
192
|
-
data.port,
|
|
193
|
-
data.database,
|
|
194
|
-
data.username,
|
|
195
|
-
encryptedPassword,
|
|
196
|
-
data.ssl,
|
|
197
|
-
data.pool_min,
|
|
198
|
-
data.pool_max,
|
|
199
|
-
createdBy
|
|
200
|
-
]);
|
|
201
|
-
}
|
|
202
|
-
async function updateDataSource(id, data, encryptedPassword) {
|
|
203
|
-
const fields = [];
|
|
204
|
-
const values = [];
|
|
205
|
-
for (const key of UPDATE_FIELDS) {
|
|
206
|
-
if (data[key] !== void 0) {
|
|
207
|
-
fields.push(`${key} = ?`);
|
|
208
|
-
values.push(data[key]);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (encryptedPassword) {
|
|
212
|
-
fields.push("password_encrypted = ?");
|
|
213
|
-
values.push(encryptedPassword);
|
|
214
|
-
}
|
|
215
|
-
if (!fields.length) return getDataSourceById(id);
|
|
216
|
-
values.push(id);
|
|
217
|
-
return getPgDb().queryOne(
|
|
218
|
-
`UPDATE data_sources SET ${fields.join(", ")} WHERE id = ? RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`,
|
|
219
|
-
values
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
function removeDataSource(id) {
|
|
223
|
-
return getPgDb().run(DELETE, [id]);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// src/datasources/datasources.service.ts
|
|
227
|
-
var pools = /* @__PURE__ */ new Map();
|
|
228
|
-
function poolKey(id, database) {
|
|
229
|
-
return database ? `${id}:${database}` : id;
|
|
230
|
-
}
|
|
231
|
-
function getPool(ds) {
|
|
232
|
-
const key = poolKey(ds.id, ds.database);
|
|
233
|
-
if (pools.has(key)) return pools.get(key);
|
|
234
|
-
const pool = ds.type === "mysql" ? createMysqlPool({
|
|
235
|
-
host: ds.host,
|
|
236
|
-
port: ds.port,
|
|
237
|
-
database: ds.database,
|
|
238
|
-
username: ds.username,
|
|
239
|
-
password: ds.password,
|
|
240
|
-
poolMax: ds.pool_max
|
|
241
|
-
}) : createPgPool({
|
|
242
|
-
host: ds.host,
|
|
243
|
-
port: ds.port,
|
|
244
|
-
database: ds.database,
|
|
245
|
-
username: ds.username,
|
|
246
|
-
password: ds.password,
|
|
247
|
-
poolMin: ds.pool_min,
|
|
248
|
-
poolMax: ds.pool_max,
|
|
249
|
-
ssl: ds.ssl
|
|
250
|
-
});
|
|
251
|
-
pools.set(key, pool);
|
|
252
|
-
logger.info("Connection pool created (ds=%s, db=%s, type=%s)", ds.id, ds.database, ds.type);
|
|
253
|
-
return pool;
|
|
254
|
-
}
|
|
255
|
-
async function getPoolForDatabase(dataSourceId, database) {
|
|
256
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
257
|
-
if (!ds) return null;
|
|
258
|
-
return getPool({ ...ds, database });
|
|
259
|
-
}
|
|
260
|
-
async function destroyPool(id) {
|
|
261
|
-
const prefix = `${id}:`;
|
|
262
|
-
for (const [key, pool] of pools) {
|
|
263
|
-
if (key === id || key.startsWith(prefix)) {
|
|
264
|
-
await pool.end();
|
|
265
|
-
pools.delete(key);
|
|
266
|
-
logger.info("Connection pool destroyed (key=%s)", key);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
async function getDataSourceWithPassword(id) {
|
|
271
|
-
const row = await getDataSourceRow(id);
|
|
272
|
-
if (!row) return null;
|
|
273
|
-
return {
|
|
274
|
-
id: row.id,
|
|
275
|
-
type: row.type,
|
|
276
|
-
host: row.host,
|
|
277
|
-
port: row.port,
|
|
278
|
-
database: row.database ?? null,
|
|
279
|
-
username: row.username,
|
|
280
|
-
password: decrypt(row.password_encrypted, config.encryptionKey),
|
|
281
|
-
ssl: row.ssl,
|
|
282
|
-
pool_min: row.pool_min,
|
|
283
|
-
pool_max: row.pool_max
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
async function datasourceList(_req, reply) {
|
|
287
|
-
return reply.send(await listDataSources());
|
|
288
|
-
}
|
|
289
|
-
async function datasourceGet(req, reply) {
|
|
290
|
-
const { id } = req.body ?? {};
|
|
291
|
-
const ds = await getDataSourceById(id);
|
|
292
|
-
if (!ds) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
|
|
293
|
-
return reply.send(ds);
|
|
294
|
-
}
|
|
295
|
-
async function datasourceCreate(req, reply) {
|
|
296
|
-
const body = req.body ?? {};
|
|
297
|
-
const ds = await insertDataSource(
|
|
298
|
-
{ ...body, ssl: body.ssl ?? false, pool_min: body.pool_min ?? 1, pool_max: body.pool_max ?? 10 },
|
|
299
|
-
encrypt(body.password, config.encryptionKey),
|
|
300
|
-
req.user.id
|
|
301
|
-
);
|
|
302
|
-
return reply.code(201).send(ds);
|
|
303
|
-
}
|
|
304
|
-
async function datasourceUpdate(req, reply) {
|
|
305
|
-
const { id, password, ...rest } = req.body ?? {};
|
|
306
|
-
const existing = await getDataSourceById(id);
|
|
307
|
-
if (!existing) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
|
|
308
|
-
const encryptedPassword = password ? encrypt(password, config.encryptionKey) : void 0;
|
|
309
|
-
await destroyPool(id);
|
|
310
|
-
const ds = await updateDataSource(id, rest, encryptedPassword);
|
|
311
|
-
return reply.send(ds);
|
|
312
|
-
}
|
|
313
|
-
async function datasourceDelete(req, reply) {
|
|
314
|
-
const { id } = req.body ?? {};
|
|
315
|
-
await destroyPool(id);
|
|
316
|
-
await removeDataSource(id);
|
|
317
|
-
return reply.code(204).send();
|
|
318
|
-
}
|
|
153
|
+
import {
|
|
154
|
+
datasourceList,
|
|
155
|
+
datasourceGet,
|
|
156
|
+
datasourceCreate as _datasourceCreate,
|
|
157
|
+
datasourceUpdate as _datasourceUpdate,
|
|
158
|
+
datasourceDelete
|
|
159
|
+
} from "@heyhru/business-dms-datasource";
|
|
160
|
+
var datasourceCreate = _datasourceCreate(config.encryptionKey);
|
|
161
|
+
var datasourceUpdate = _datasourceUpdate(config.encryptionKey);
|
|
319
162
|
|
|
320
163
|
// src/datasources/datasources.controller.ts
|
|
321
164
|
function datasourceController(app) {
|
|
@@ -328,64 +171,12 @@ function datasourceController(app) {
|
|
|
328
171
|
|
|
329
172
|
// src/sql/sql.service.ts
|
|
330
173
|
import NodeSqlParser from "node-sql-parser";
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
INSERT INTO audit_logs (user_id, data_source_id, action, sql_text, result_summary, ip_address)
|
|
338
|
-
VALUES (?, ?, ?, ?, ?, ?)`;
|
|
339
|
-
var LIST2 = `
|
|
340
|
-
SELECT al.*, u.username
|
|
341
|
-
FROM audit_logs al
|
|
342
|
-
LEFT JOIN users u ON al.user_id = u.id
|
|
343
|
-
WHERE 1=1`;
|
|
344
|
-
|
|
345
|
-
// src/audit/audit.model.ts
|
|
346
|
-
function insertAuditLog(entry) {
|
|
347
|
-
return getPgDb2().run(INSERT, [
|
|
348
|
-
entry.userId,
|
|
349
|
-
entry.dataSourceId ?? null,
|
|
350
|
-
entry.action,
|
|
351
|
-
entry.sqlText ?? null,
|
|
352
|
-
entry.resultSummary ?? null,
|
|
353
|
-
entry.ipAddress ?? null
|
|
354
|
-
]);
|
|
355
|
-
}
|
|
356
|
-
async function queryAuditLogs(filters) {
|
|
357
|
-
let query = LIST2;
|
|
358
|
-
const params = [];
|
|
359
|
-
if (filters?.userId) {
|
|
360
|
-
query += " AND al.user_id = ?";
|
|
361
|
-
params.push(filters.userId);
|
|
362
|
-
}
|
|
363
|
-
if (filters?.dataSourceId) {
|
|
364
|
-
query += " AND al.data_source_id = ?";
|
|
365
|
-
params.push(filters.dataSourceId);
|
|
366
|
-
}
|
|
367
|
-
query += " ORDER BY al.created_at DESC LIMIT ? OFFSET ?";
|
|
368
|
-
params.push(filters?.limit ?? 50, filters?.offset ?? 0);
|
|
369
|
-
return getPgDb2().query(query, params);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// src/audit/audit.service.ts
|
|
373
|
-
function writeAuditLog(entry) {
|
|
374
|
-
return insertAuditLog(entry);
|
|
375
|
-
}
|
|
376
|
-
async function logList(req, reply) {
|
|
377
|
-
const { userId, dataSourceId, limit, offset } = req.body ?? {};
|
|
378
|
-
return reply.send(
|
|
379
|
-
await queryAuditLogs({
|
|
380
|
-
userId,
|
|
381
|
-
dataSourceId,
|
|
382
|
-
limit: limit ? Number(limit) : void 0,
|
|
383
|
-
offset: offset ? Number(offset) : void 0
|
|
384
|
-
})
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// src/sql/sql.service.ts
|
|
174
|
+
import {
|
|
175
|
+
getDataSourceWithPassword,
|
|
176
|
+
getPool,
|
|
177
|
+
getPoolForDatabase
|
|
178
|
+
} from "@heyhru/business-dms-datasource";
|
|
179
|
+
import { writeAuditLog } from "@heyhru/business-dms-audit";
|
|
389
180
|
var parser = new NodeSqlParser.Parser();
|
|
390
181
|
function sqlParse(sql) {
|
|
391
182
|
try {
|
|
@@ -421,8 +212,8 @@ async function sqlExecute({ dataSourceId, database, sql, userId, ip }) {
|
|
|
421
212
|
if (category !== "select") {
|
|
422
213
|
throw new Error("\u4EC5\u5141\u8BB8\u76F4\u63A5\u6267\u884C SELECT \u8BED\u53E5");
|
|
423
214
|
}
|
|
424
|
-
const pool = database ? await getPoolForDatabase(dataSourceId, database) : await (async () => {
|
|
425
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
215
|
+
const pool = database ? await getPoolForDatabase(dataSourceId, database, config.encryptionKey) : await (async () => {
|
|
216
|
+
const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
|
|
426
217
|
return ds ? getPool(ds) : null;
|
|
427
218
|
})();
|
|
428
219
|
if (!pool) throw new Error("\u6570\u636E\u6E90\u672A\u627E\u5230");
|
|
@@ -442,7 +233,7 @@ async function postSqlDatabases(req, reply) {
|
|
|
442
233
|
if (!dataSourceId) {
|
|
443
234
|
return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u4E0D\u80FD\u4E3A\u7A7A" });
|
|
444
235
|
}
|
|
445
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
236
|
+
const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
|
|
446
237
|
if (!ds) return reply.code(404).send({ error: "\u6570\u636E\u6E90\u672A\u627E\u5230" });
|
|
447
238
|
const pool = getPool({
|
|
448
239
|
...ds,
|
|
@@ -465,9 +256,9 @@ async function postSqlTables(req, reply) {
|
|
|
465
256
|
if (!dataSourceId || !database) {
|
|
466
257
|
return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u548C\u6570\u636E\u5E93\u540D\u4E0D\u80FD\u4E3A\u7A7A" });
|
|
467
258
|
}
|
|
468
|
-
const pool = await getPoolForDatabase(dataSourceId, database);
|
|
259
|
+
const pool = await getPoolForDatabase(dataSourceId, database, config.encryptionKey);
|
|
469
260
|
if (!pool) return reply.code(404).send({ error: "\u6570\u636E\u6E90\u672A\u627E\u5230" });
|
|
470
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
261
|
+
const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
|
|
471
262
|
const sql = ds.type === "mysql" ? `SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '${database}' ORDER BY TABLE_NAME` : "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename";
|
|
472
263
|
try {
|
|
473
264
|
const rows = await pool.execute(sql);
|
|
@@ -489,161 +280,16 @@ function sqlController(app) {
|
|
|
489
280
|
app.post("/sql/tables", postSqlTables);
|
|
490
281
|
}
|
|
491
282
|
|
|
492
|
-
// src/approvals/approvals.model.ts
|
|
493
|
-
import { getPgDb as getPgDb3 } from "@heyhru/server-plugin-pg";
|
|
494
|
-
|
|
495
|
-
// src/approvals/approvals.sql.ts
|
|
496
|
-
var FIND_BY_ID2 = `
|
|
497
|
-
SELECT *
|
|
498
|
-
FROM approvals
|
|
499
|
-
WHERE id = ?`;
|
|
500
|
-
var CREATE2 = `
|
|
501
|
-
INSERT INTO approvals (data_source_id, sql_text, submitted_by)
|
|
502
|
-
VALUES (?, ?, ?)
|
|
503
|
-
RETURNING *`;
|
|
504
|
-
var UPDATE_REVIEW = () => `
|
|
505
|
-
UPDATE approvals
|
|
506
|
-
SET status = ?, reviewed_by = ?, reject_reason = ?, updated_at = NOW()
|
|
507
|
-
WHERE id = ?
|
|
508
|
-
RETURNING *`;
|
|
509
|
-
var UPDATE_EXECUTING = () => `
|
|
510
|
-
UPDATE approvals
|
|
511
|
-
SET status = 'executing', updated_at = NOW()
|
|
512
|
-
WHERE id = ?`;
|
|
513
|
-
var UPDATE_RESULT = () => `
|
|
514
|
-
UPDATE approvals
|
|
515
|
-
SET status = ?, execute_result = ?, updated_at = NOW()
|
|
516
|
-
WHERE id = ?`;
|
|
517
|
-
|
|
518
|
-
// src/approvals/approvals.model.ts
|
|
519
|
-
async function listApprovals(filters) {
|
|
520
|
-
let query = "SELECT * FROM approvals WHERE 1=1";
|
|
521
|
-
const params = [];
|
|
522
|
-
if (filters?.status) {
|
|
523
|
-
query += " AND status = ?";
|
|
524
|
-
params.push(filters.status);
|
|
525
|
-
}
|
|
526
|
-
if (filters?.submittedBy) {
|
|
527
|
-
query += " AND submitted_by = ?";
|
|
528
|
-
params.push(filters.submittedBy);
|
|
529
|
-
}
|
|
530
|
-
query += " ORDER BY created_at DESC";
|
|
531
|
-
return getPgDb3().query(query, params);
|
|
532
|
-
}
|
|
533
|
-
function getApprovalById(id) {
|
|
534
|
-
return getPgDb3().queryOne(FIND_BY_ID2, [id]);
|
|
535
|
-
}
|
|
536
|
-
function insertApproval(dataSourceId, sqlText, submittedBy) {
|
|
537
|
-
return getPgDb3().queryOne(CREATE2, [dataSourceId, sqlText, submittedBy]);
|
|
538
|
-
}
|
|
539
|
-
function updateReview(id, status, reviewedBy, rejectReason) {
|
|
540
|
-
return getPgDb3().queryOne(UPDATE_REVIEW(), [status, reviewedBy, rejectReason, id]);
|
|
541
|
-
}
|
|
542
|
-
function setExecuting(id) {
|
|
543
|
-
return getPgDb3().run(UPDATE_EXECUTING(), [id]);
|
|
544
|
-
}
|
|
545
|
-
function setExecuteResult(id, status, result) {
|
|
546
|
-
return getPgDb3().run(UPDATE_RESULT(), [status, result, id]);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
283
|
// src/approvals/approvals.service.ts
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
async function approvalGet(req, reply) {
|
|
560
|
-
const { id } = req.body ?? {};
|
|
561
|
-
const approval = await getApprovalById(id);
|
|
562
|
-
if (!approval) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
|
|
563
|
-
return reply.send(approval);
|
|
564
|
-
}
|
|
565
|
-
async function approvalCreate(req, reply) {
|
|
566
|
-
const { dataSourceId, sql } = req.body ?? {};
|
|
567
|
-
if (!dataSourceId || !sql) {
|
|
568
|
-
return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u548C SQL \u4E0D\u80FD\u4E3A\u7A7A" });
|
|
569
|
-
}
|
|
570
|
-
const approval = await insertApproval(dataSourceId, sql, req.user.id);
|
|
571
|
-
req.log.info("Approval submitted (user=%s)", req.user.id);
|
|
572
|
-
return reply.code(201).send(approval);
|
|
573
|
-
}
|
|
574
|
-
async function reviewApproval(id, reviewerId, decision, rejectReason) {
|
|
575
|
-
const approval = await getApprovalById(id);
|
|
576
|
-
if (!approval || approval["status"] !== "pending") {
|
|
577
|
-
throw new Error("\u5BA1\u6279\u8BB0\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u5728\u5F85\u5BA1\u6279\u72B6\u6001");
|
|
578
|
-
}
|
|
579
|
-
if (approval["submitted_by"] === reviewerId) {
|
|
580
|
-
throw new Error("\u4E0D\u80FD\u5BA1\u6279\u81EA\u5DF1\u63D0\u4EA4\u7684\u8BF7\u6C42");
|
|
581
|
-
}
|
|
582
|
-
return updateReview(id, decision, reviewerId, rejectReason ?? null);
|
|
583
|
-
}
|
|
584
|
-
async function approvalApprove(req, reply) {
|
|
585
|
-
const { id } = req.body ?? {};
|
|
586
|
-
try {
|
|
587
|
-
const result = await reviewApproval(id, req.user.id, "approved");
|
|
588
|
-
req.log.info("Approval approved (id=%s, reviewer=%s)", id, req.user.id);
|
|
589
|
-
return reply.send(result);
|
|
590
|
-
} catch (err) {
|
|
591
|
-
req.log.warn(err, "Approve failed (id=%s, reviewer=%s)", id, req.user.id);
|
|
592
|
-
return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
async function approvalReject(req, reply) {
|
|
596
|
-
const { id, reason } = req.body ?? {};
|
|
597
|
-
try {
|
|
598
|
-
const result = await reviewApproval(id, req.user.id, "rejected", reason);
|
|
599
|
-
req.log.info("Approval rejected (id=%s, reviewer=%s)", id, req.user.id);
|
|
600
|
-
return reply.send(result);
|
|
601
|
-
} catch (err) {
|
|
602
|
-
req.log.warn(err, "Reject failed (id=%s, reviewer=%s)", id, req.user.id);
|
|
603
|
-
return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
async function approvalExecute(req, reply) {
|
|
607
|
-
const { id } = req.body ?? {};
|
|
608
|
-
const ip = req.headers["x-forwarded-for"] ?? req.headers["x-real-ip"] ?? "unknown";
|
|
609
|
-
try {
|
|
610
|
-
const result = await doExecuteApproval(id, req.user.id, ip, req.log);
|
|
611
|
-
return reply.send(result);
|
|
612
|
-
} catch (err) {
|
|
613
|
-
req.log.error(err, "Execute approval failed (id=%s, user=%s)", id, req.user.id);
|
|
614
|
-
return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
async function doExecuteApproval(id, userId, ip, log) {
|
|
618
|
-
const approval = await getApprovalById(id);
|
|
619
|
-
if (!approval || !["approved", "execute_failed"].includes(approval["status"])) {
|
|
620
|
-
throw new Error("\u5BA1\u6279\u8BB0\u5F55\u4E0D\u5B58\u5728\u6216\u672A\u901A\u8FC7\u5BA1\u6279");
|
|
621
|
-
}
|
|
622
|
-
await setExecuting(id);
|
|
623
|
-
try {
|
|
624
|
-
const ds = await getDataSourceWithPassword(approval["data_source_id"]);
|
|
625
|
-
if (!ds) throw new Error("\u6570\u636E\u6E90\u672A\u627E\u5230");
|
|
626
|
-
const pool = getPool(ds);
|
|
627
|
-
const rows = await pool.execute(approval["sql_text"]);
|
|
628
|
-
const result = `${rows.length} rows affected`;
|
|
629
|
-
await setExecuteResult(id, "executed", result);
|
|
630
|
-
log.info("Approval executed (id=%s, user=%s)", id, userId);
|
|
631
|
-
await writeAuditLog({
|
|
632
|
-
userId,
|
|
633
|
-
dataSourceId: approval["data_source_id"],
|
|
634
|
-
action: "DML_EXECUTE",
|
|
635
|
-
sqlText: approval["sql_text"],
|
|
636
|
-
resultSummary: result,
|
|
637
|
-
ipAddress: ip
|
|
638
|
-
});
|
|
639
|
-
return getApprovalById(id);
|
|
640
|
-
} catch (err) {
|
|
641
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
642
|
-
await setExecuteResult(id, "execute_failed", message);
|
|
643
|
-
log.error(err, "Approval execute_failed (id=%s)", id);
|
|
644
|
-
throw err;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
284
|
+
import {
|
|
285
|
+
approvalList,
|
|
286
|
+
approvalGet,
|
|
287
|
+
approvalCreate,
|
|
288
|
+
approvalApprove,
|
|
289
|
+
approvalReject,
|
|
290
|
+
approvalExecute as _approvalExecute
|
|
291
|
+
} from "@heyhru/business-dms-approval";
|
|
292
|
+
var approvalExecute = _approvalExecute(config.encryptionKey);
|
|
647
293
|
|
|
648
294
|
// src/approvals/approvals.controller.ts
|
|
649
295
|
function approvalController(app) {
|
|
@@ -656,10 +302,25 @@ function approvalController(app) {
|
|
|
656
302
|
}
|
|
657
303
|
|
|
658
304
|
// src/audit/audit.controller.ts
|
|
305
|
+
import { logList } from "@heyhru/business-dms-audit";
|
|
659
306
|
function auditController(app) {
|
|
660
307
|
app.post("/logs/list", logList);
|
|
661
308
|
}
|
|
662
309
|
|
|
310
|
+
// src/saved-sqls/saved-sqls.controller.ts
|
|
311
|
+
import {
|
|
312
|
+
savedSqlList,
|
|
313
|
+
savedSqlCreate,
|
|
314
|
+
savedSqlUpdate,
|
|
315
|
+
savedSqlDelete
|
|
316
|
+
} from "@heyhru/business-dms-saved-sql";
|
|
317
|
+
function savedSqlController(app) {
|
|
318
|
+
app.post("/saved-sqls/list", savedSqlList);
|
|
319
|
+
app.post("/saved-sqls/create", savedSqlCreate);
|
|
320
|
+
app.post("/saved-sqls/update", savedSqlUpdate);
|
|
321
|
+
app.post("/saved-sqls/delete", savedSqlDelete);
|
|
322
|
+
}
|
|
323
|
+
|
|
663
324
|
// src/app.ts
|
|
664
325
|
async function buildApp() {
|
|
665
326
|
const app = Fastify({ loggerInstance: logger });
|
|
@@ -677,11 +338,12 @@ async function buildApp() {
|
|
|
677
338
|
sqlController(app);
|
|
678
339
|
approvalController(app);
|
|
679
340
|
auditController(app);
|
|
341
|
+
savedSqlController(app);
|
|
680
342
|
return app;
|
|
681
343
|
}
|
|
682
344
|
|
|
683
345
|
// src/migrate/runner.ts
|
|
684
|
-
import { getPgDb
|
|
346
|
+
import { getPgDb } from "@heyhru/server-plugin-pg";
|
|
685
347
|
|
|
686
348
|
// src/migrate/migrations.ts
|
|
687
349
|
var migrations = [
|
|
@@ -768,12 +430,28 @@ CREATE INDEX IF NOT EXISTS idx_approvals_submitted_by ON approvals(submitted_by)
|
|
|
768
430
|
{
|
|
769
431
|
name: "005_data_sources_add_ssl.sql",
|
|
770
432
|
sql: () => `ALTER TABLE data_sources ADD COLUMN IF NOT EXISTS ssl BOOLEAN NOT NULL DEFAULT FALSE;`
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "006_saved_sqls.sql",
|
|
436
|
+
sql: () => `
|
|
437
|
+
CREATE TABLE IF NOT EXISTS saved_sqls (
|
|
438
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
439
|
+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
440
|
+
name TEXT NOT NULL,
|
|
441
|
+
sql_text TEXT NOT NULL,
|
|
442
|
+
data_source_id UUID REFERENCES data_sources(id) ON DELETE SET NULL,
|
|
443
|
+
database TEXT,
|
|
444
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
445
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
CREATE INDEX IF NOT EXISTS idx_saved_sqls_user_id ON saved_sqls(user_id);`
|
|
771
449
|
}
|
|
772
450
|
];
|
|
773
451
|
|
|
774
452
|
// src/migrate/runner.ts
|
|
775
453
|
async function runMigrations() {
|
|
776
|
-
const db =
|
|
454
|
+
const db = getPgDb();
|
|
777
455
|
await db.exec(`CREATE TABLE IF NOT EXISTS _migrations (
|
|
778
456
|
name TEXT PRIMARY KEY,
|
|
779
457
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.4.1",
|
|
7
7
|
"description": "DMS backend API server built on Fastify",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.mjs",
|
|
@@ -19,12 +19,16 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@fastify/cors": "^11.0.0",
|
|
22
|
-
"@heyhru/business-dms-
|
|
23
|
-
"@heyhru/
|
|
24
|
-
"@heyhru/
|
|
25
|
-
"@heyhru/
|
|
26
|
-
"@heyhru/
|
|
27
|
-
"@heyhru/
|
|
22
|
+
"@heyhru/business-dms-approval": "0.4.0",
|
|
23
|
+
"@heyhru/business-dms-audit": "0.4.0",
|
|
24
|
+
"@heyhru/business-dms-datasource": "0.4.0",
|
|
25
|
+
"@heyhru/business-dms-saved-sql": "0.4.0",
|
|
26
|
+
"@heyhru/business-dms-user": "0.4.0",
|
|
27
|
+
"@heyhru/common-util-logger": "0.4.0",
|
|
28
|
+
"@heyhru/server-plugin-jwt": "0.4.0",
|
|
29
|
+
"@heyhru/server-plugin-mysql": "0.4.0",
|
|
30
|
+
"@heyhru/server-plugin-pg": "0.4.0",
|
|
31
|
+
"@heyhru/server-util-crypto": "0.4.0",
|
|
28
32
|
"fastify": "^5.8.4",
|
|
29
33
|
"node-sql-parser": "^5.4.0"
|
|
30
34
|
},
|
|
@@ -36,5 +40,5 @@
|
|
|
36
40
|
"typescript": "^6.0.2",
|
|
37
41
|
"vitest": "^4.1.2"
|
|
38
42
|
},
|
|
39
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "0100f7a8b3290424cec7e4fe4ae623db15ba4039"
|
|
40
44
|
}
|