@heyhru/app-dms-server 0.3.4 → 0.4.0
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 +64 -391
- package/package.json +12 -8
package/dist/index.js
CHANGED
|
@@ -145,177 +145,15 @@ function userController(app) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// 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
|
-
}
|
|
148
|
+
import {
|
|
149
|
+
datasourceList,
|
|
150
|
+
datasourceGet,
|
|
151
|
+
datasourceCreate as _datasourceCreate,
|
|
152
|
+
datasourceUpdate as _datasourceUpdate,
|
|
153
|
+
datasourceDelete
|
|
154
|
+
} from "@heyhru/business-dms-datasource";
|
|
155
|
+
var datasourceCreate = _datasourceCreate(config.encryptionKey);
|
|
156
|
+
var datasourceUpdate = _datasourceUpdate(config.encryptionKey);
|
|
319
157
|
|
|
320
158
|
// src/datasources/datasources.controller.ts
|
|
321
159
|
function datasourceController(app) {
|
|
@@ -328,64 +166,12 @@ function datasourceController(app) {
|
|
|
328
166
|
|
|
329
167
|
// src/sql/sql.service.ts
|
|
330
168
|
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
|
|
169
|
+
import {
|
|
170
|
+
getDataSourceWithPassword,
|
|
171
|
+
getPool,
|
|
172
|
+
getPoolForDatabase
|
|
173
|
+
} from "@heyhru/business-dms-datasource";
|
|
174
|
+
import { writeAuditLog } from "@heyhru/business-dms-audit";
|
|
389
175
|
var parser = new NodeSqlParser.Parser();
|
|
390
176
|
function sqlParse(sql) {
|
|
391
177
|
try {
|
|
@@ -421,8 +207,8 @@ async function sqlExecute({ dataSourceId, database, sql, userId, ip }) {
|
|
|
421
207
|
if (category !== "select") {
|
|
422
208
|
throw new Error("\u4EC5\u5141\u8BB8\u76F4\u63A5\u6267\u884C SELECT \u8BED\u53E5");
|
|
423
209
|
}
|
|
424
|
-
const pool = database ? await getPoolForDatabase(dataSourceId, database) : await (async () => {
|
|
425
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
210
|
+
const pool = database ? await getPoolForDatabase(dataSourceId, database, config.encryptionKey) : await (async () => {
|
|
211
|
+
const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
|
|
426
212
|
return ds ? getPool(ds) : null;
|
|
427
213
|
})();
|
|
428
214
|
if (!pool) throw new Error("\u6570\u636E\u6E90\u672A\u627E\u5230");
|
|
@@ -442,13 +228,13 @@ async function postSqlDatabases(req, reply) {
|
|
|
442
228
|
if (!dataSourceId) {
|
|
443
229
|
return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u4E0D\u80FD\u4E3A\u7A7A" });
|
|
444
230
|
}
|
|
445
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
231
|
+
const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
|
|
446
232
|
if (!ds) return reply.code(404).send({ error: "\u6570\u636E\u6E90\u672A\u627E\u5230" });
|
|
447
233
|
const pool = getPool({
|
|
448
234
|
...ds,
|
|
449
235
|
database: ds.database ?? (ds.type === "postgres" ? "postgres" : "")
|
|
450
236
|
});
|
|
451
|
-
const sql = ds.type === "mysql" ? "SHOW DATABASES" : "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname";
|
|
237
|
+
const sql = ds.type === "mysql" ? "SHOW DATABASES" : "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('cloudsqladmin') ORDER BY datname";
|
|
452
238
|
try {
|
|
453
239
|
const rows = await pool.execute(sql);
|
|
454
240
|
const names = rows.map(
|
|
@@ -465,9 +251,9 @@ async function postSqlTables(req, reply) {
|
|
|
465
251
|
if (!dataSourceId || !database) {
|
|
466
252
|
return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u548C\u6570\u636E\u5E93\u540D\u4E0D\u80FD\u4E3A\u7A7A" });
|
|
467
253
|
}
|
|
468
|
-
const pool = await getPoolForDatabase(dataSourceId, database);
|
|
254
|
+
const pool = await getPoolForDatabase(dataSourceId, database, config.encryptionKey);
|
|
469
255
|
if (!pool) return reply.code(404).send({ error: "\u6570\u636E\u6E90\u672A\u627E\u5230" });
|
|
470
|
-
const ds = await getDataSourceWithPassword(dataSourceId);
|
|
256
|
+
const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
|
|
471
257
|
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
258
|
try {
|
|
473
259
|
const rows = await pool.execute(sql);
|
|
@@ -489,161 +275,16 @@ function sqlController(app) {
|
|
|
489
275
|
app.post("/sql/tables", postSqlTables);
|
|
490
276
|
}
|
|
491
277
|
|
|
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
278
|
// 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
|
-
}
|
|
279
|
+
import {
|
|
280
|
+
approvalList,
|
|
281
|
+
approvalGet,
|
|
282
|
+
approvalCreate,
|
|
283
|
+
approvalApprove,
|
|
284
|
+
approvalReject,
|
|
285
|
+
approvalExecute as _approvalExecute
|
|
286
|
+
} from "@heyhru/business-dms-approval";
|
|
287
|
+
var approvalExecute = _approvalExecute(config.encryptionKey);
|
|
647
288
|
|
|
648
289
|
// src/approvals/approvals.controller.ts
|
|
649
290
|
function approvalController(app) {
|
|
@@ -656,10 +297,25 @@ function approvalController(app) {
|
|
|
656
297
|
}
|
|
657
298
|
|
|
658
299
|
// src/audit/audit.controller.ts
|
|
300
|
+
import { logList } from "@heyhru/business-dms-audit";
|
|
659
301
|
function auditController(app) {
|
|
660
302
|
app.post("/logs/list", logList);
|
|
661
303
|
}
|
|
662
304
|
|
|
305
|
+
// src/saved-sqls/saved-sqls.controller.ts
|
|
306
|
+
import {
|
|
307
|
+
savedSqlList,
|
|
308
|
+
savedSqlCreate,
|
|
309
|
+
savedSqlUpdate,
|
|
310
|
+
savedSqlDelete
|
|
311
|
+
} from "@heyhru/business-dms-saved-sql";
|
|
312
|
+
function savedSqlController(app) {
|
|
313
|
+
app.post("/saved-sqls/list", savedSqlList);
|
|
314
|
+
app.post("/saved-sqls/create", savedSqlCreate);
|
|
315
|
+
app.post("/saved-sqls/update", savedSqlUpdate);
|
|
316
|
+
app.post("/saved-sqls/delete", savedSqlDelete);
|
|
317
|
+
}
|
|
318
|
+
|
|
663
319
|
// src/app.ts
|
|
664
320
|
async function buildApp() {
|
|
665
321
|
const app = Fastify({ loggerInstance: logger });
|
|
@@ -677,11 +333,12 @@ async function buildApp() {
|
|
|
677
333
|
sqlController(app);
|
|
678
334
|
approvalController(app);
|
|
679
335
|
auditController(app);
|
|
336
|
+
savedSqlController(app);
|
|
680
337
|
return app;
|
|
681
338
|
}
|
|
682
339
|
|
|
683
340
|
// src/migrate/runner.ts
|
|
684
|
-
import { getPgDb
|
|
341
|
+
import { getPgDb } from "@heyhru/server-plugin-pg";
|
|
685
342
|
|
|
686
343
|
// src/migrate/migrations.ts
|
|
687
344
|
var migrations = [
|
|
@@ -768,12 +425,28 @@ CREATE INDEX IF NOT EXISTS idx_approvals_submitted_by ON approvals(submitted_by)
|
|
|
768
425
|
{
|
|
769
426
|
name: "005_data_sources_add_ssl.sql",
|
|
770
427
|
sql: () => `ALTER TABLE data_sources ADD COLUMN IF NOT EXISTS ssl BOOLEAN NOT NULL DEFAULT FALSE;`
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "006_saved_sqls.sql",
|
|
431
|
+
sql: () => `
|
|
432
|
+
CREATE TABLE IF NOT EXISTS saved_sqls (
|
|
433
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
434
|
+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
435
|
+
name TEXT NOT NULL,
|
|
436
|
+
sql_text TEXT NOT NULL,
|
|
437
|
+
data_source_id UUID REFERENCES data_sources(id) ON DELETE SET NULL,
|
|
438
|
+
database TEXT,
|
|
439
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
440
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
CREATE INDEX IF NOT EXISTS idx_saved_sqls_user_id ON saved_sqls(user_id);`
|
|
771
444
|
}
|
|
772
445
|
];
|
|
773
446
|
|
|
774
447
|
// src/migrate/runner.ts
|
|
775
448
|
async function runMigrations() {
|
|
776
|
-
const db =
|
|
449
|
+
const db = getPgDb();
|
|
777
450
|
await db.exec(`CREATE TABLE IF NOT EXISTS _migrations (
|
|
778
451
|
name TEXT PRIMARY KEY,
|
|
779
452
|
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.0",
|
|
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": "48f00e1f0b1ca5d0789c0df9384a12f2c8e847c4"
|
|
40
44
|
}
|