@heyhru/app-dms-server 0.1.5 → 0.1.6

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 CHANGED
@@ -1,5 +1,31 @@
1
- // src/index.ts
2
- import { createSqlite } from "@heyhru/server-util-sqlite";
1
+ import "./chunk-QGM4M3NI.js";
2
+
3
+ // src/db/index.ts
4
+ var _db = null;
5
+ var _driver = "sqlite";
6
+ function parseDbDriver(url) {
7
+ if (url.startsWith("postgres://") || url.startsWith("postgresql://")) return "postgres";
8
+ return "sqlite";
9
+ }
10
+ async function createDmsDb(url) {
11
+ if (_db) throw new Error("DmsDb already initialized. Call close() first.");
12
+ _driver = parseDbDriver(url);
13
+ if (_driver === "postgres") {
14
+ const { createPgAdapter } = await import("./pg.adapter-BNI42SHT.js");
15
+ _db = createPgAdapter(url);
16
+ } else {
17
+ const { createSqliteAdapter } = await import("./sqlite.adapter-SCNLOKBD.js");
18
+ _db = createSqliteAdapter(url.replace("sqlite://", ""));
19
+ }
20
+ return _db;
21
+ }
22
+ function getDb() {
23
+ if (!_db) throw new Error("DmsDb not initialized. Call createDmsDb() first.");
24
+ return _db;
25
+ }
26
+ function getDriver() {
27
+ return _driver;
28
+ }
3
29
 
4
30
  // src/app.ts
5
31
  import Fastify from "fastify";
@@ -85,8 +111,10 @@ import { hashPassword as hashPassword2, verifyPassword } from "@heyhru/server-ut
85
111
  // src/users/users.service.ts
86
112
  import { hashPassword } from "@heyhru/server-util-crypto";
87
113
 
88
- // src/users/users.model.ts
89
- import { getSqlite as getDb } from "@heyhru/server-util-sqlite";
114
+ // src/db/dialect.ts
115
+ function nowExpr() {
116
+ return getDriver() === "postgres" ? "NOW()" : "datetime('now')";
117
+ }
90
118
 
91
119
  // src/users/users.sql.ts
92
120
  var LIST = `
@@ -105,9 +133,9 @@ var CREATE = `
105
133
  INSERT INTO users (username, email, password_hash, role)
106
134
  VALUES (?, ?, ?, ?)
107
135
  RETURNING id, username, email, role, created_at`;
108
- var UPDATE_PASSWORD = `
136
+ var UPDATE_PASSWORD = () => `
109
137
  UPDATE users
110
- SET password_hash = ?, updated_at = datetime('now')
138
+ SET password_hash = ?, updated_at = ${nowExpr()}
111
139
  WHERE id = ?`;
112
140
  var DELETE = `
113
141
  DELETE FROM users
@@ -115,18 +143,18 @@ WHERE id = ?`;
115
143
 
116
144
  // src/users/users.model.ts
117
145
  function listUsers() {
118
- return getDb().prepare(LIST).all();
146
+ return getDb().query(LIST);
119
147
  }
120
148
  function getUserById(id) {
121
- return getDb().prepare(FIND_BY_ID).get(id);
149
+ return getDb().queryOne(FIND_BY_ID, [id]);
122
150
  }
123
151
  function getUserByUsername(username) {
124
- return getDb().prepare(FIND_BY_USERNAME).get(username);
152
+ return getDb().queryOne(FIND_BY_USERNAME, [username]);
125
153
  }
126
154
  function createUserRow(username, email, hash, role) {
127
- return getDb().prepare(CREATE).get(username, email, hash, role);
155
+ return getDb().queryOne(CREATE, [username, email, hash, role]);
128
156
  }
129
- function updateUserRow(id, data) {
157
+ async function updateUserRow(id, data) {
130
158
  const fields = [];
131
159
  const values = [];
132
160
  if (data.email) {
@@ -138,18 +166,18 @@ function updateUserRow(id, data) {
138
166
  values.push(data.role);
139
167
  }
140
168
  if (!fields.length) return getUserById(id);
141
- fields.push("updated_at = datetime('now')");
169
+ fields.push(`updated_at = ${nowExpr()}`);
142
170
  values.push(id);
143
- const bound = values;
144
- return getDb().prepare(
145
- `UPDATE users SET ${fields.join(", ")} WHERE id = ? RETURNING id, username, email, role, created_at`
146
- ).get(...bound);
171
+ return getDb().queryOne(
172
+ `UPDATE users SET ${fields.join(", ")} WHERE id = ? RETURNING id, username, email, role, created_at`,
173
+ values
174
+ );
147
175
  }
148
176
  function deleteUser(id) {
149
- return getDb().prepare(DELETE).run(id);
177
+ return getDb().run(DELETE, [id]);
150
178
  }
151
179
  function updatePasswordHash(id, hash) {
152
- return getDb().prepare(UPDATE_PASSWORD).run(hash, id);
180
+ return getDb().run(UPDATE_PASSWORD(), [hash, id]);
153
181
  }
154
182
 
155
183
  // src/users/users.service.ts
@@ -159,30 +187,30 @@ function getUserByUsername2(username) {
159
187
  function updateUserPassword(id, hash) {
160
188
  return updatePasswordHash(id, hash);
161
189
  }
162
- function userList(_req, reply) {
163
- return reply.send(listUsers());
190
+ async function userList(_req, reply) {
191
+ return reply.send(await listUsers());
164
192
  }
165
- function userGet(req, reply) {
193
+ async function userGet(req, reply) {
166
194
  const { id } = req.body ?? {};
167
- const user = getUserById(id);
195
+ const user = await getUserById(id);
168
196
  if (!user) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
169
197
  return reply.send(user);
170
198
  }
171
199
  async function userCreate(req, reply) {
172
200
  const body = req.body ?? {};
173
201
  const hash = await hashPassword(body.password);
174
- const user = createUserRow(body.username, body.email, hash, body.role);
202
+ const user = await createUserRow(body.username, body.email, hash, body.role);
175
203
  return reply.code(201).send(user);
176
204
  }
177
- function userUpdate(req, reply) {
205
+ async function userUpdate(req, reply) {
178
206
  const { id, ...rest } = req.body ?? {};
179
- const user = updateUserRow(id, rest);
207
+ const user = await updateUserRow(id, rest);
180
208
  if (!user) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
181
209
  return reply.send(user);
182
210
  }
183
- function userDelete(req, reply) {
211
+ async function userDelete(req, reply) {
184
212
  const { id } = req.body ?? {};
185
- deleteUser(id);
213
+ await deleteUser(id);
186
214
  return reply.code(204).send();
187
215
  }
188
216
 
@@ -192,7 +220,7 @@ async function authLogin(req, reply) {
192
220
  if (!username || !password) {
193
221
  return reply.code(400).send({ error: "\u7528\u6237\u540D\u548C\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A" });
194
222
  }
195
- const user = getUserByUsername2(username);
223
+ const user = await getUserByUsername2(username);
196
224
  if (!user || !await verifyPassword(password, user.password_hash)) {
197
225
  logger.warn("Login failed for user: %s", username);
198
226
  return reply.code(401).send({ error: "\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF" });
@@ -214,12 +242,12 @@ async function authChangePassword(req, reply) {
214
242
  if (!currentPassword || !newPassword) {
215
243
  return reply.code(400).send({ error: "\u5F53\u524D\u5BC6\u7801\u548C\u65B0\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A" });
216
244
  }
217
- const user = getUserByUsername2(req.user.username);
245
+ const user = await getUserByUsername2(req.user.username);
218
246
  if (!user || !await verifyPassword(currentPassword, user.password_hash)) {
219
247
  return reply.code(400).send({ error: "\u5F53\u524D\u5BC6\u7801\u4E0D\u6B63\u786E" });
220
248
  }
221
249
  const hash = await hashPassword2(newPassword);
222
- updateUserPassword(user.id, hash);
250
+ await updateUserPassword(user.id, hash);
223
251
  logger.info("Password changed for user: %s", req.user.username);
224
252
  return reply.send({ ok: true });
225
253
  }
@@ -246,16 +274,13 @@ import { encrypt, decrypt } from "@heyhru/server-util-crypto";
246
274
  import { createPool as createMysqlPool } from "@heyhru/server-util-mysql";
247
275
  import { createPool as createPgPool } from "@heyhru/server-util-pg";
248
276
 
249
- // src/datasources/datasources.model.ts
250
- import { getSqlite as getDb2 } from "@heyhru/server-util-sqlite";
251
-
252
277
  // src/datasources/datasources.sql.ts
253
278
  var LIST2 = `
254
- SELECT id, name, type, host, port, database, username, pool_min, pool_max, created_by, created_at
279
+ SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
255
280
  FROM data_sources
256
281
  ORDER BY created_at DESC`;
257
282
  var FIND_BY_ID2 = `
258
- SELECT id, name, type, host, port, database, username, pool_min, pool_max, created_by, created_at
283
+ SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
259
284
  FROM data_sources
260
285
  WHERE id = ?`;
261
286
  var FIND_WITH_PASSWORD = `
@@ -263,26 +288,26 @@ SELECT *
263
288
  FROM data_sources
264
289
  WHERE id = ?`;
265
290
  var CREATE2 = `
266
- INSERT INTO data_sources (name, type, host, port, database, username, password_encrypted, pool_min, pool_max, created_by)
267
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
268
- RETURNING id, name, type, host, port, database, username, pool_min, pool_max, created_by, created_at`;
269
- var UPDATE_FIELDS = ["name", "type", "host", "port", "database", "username", "pool_min", "pool_max"];
291
+ INSERT INTO data_sources (name, type, host, port, database, username, password_encrypted, ssl, pool_min, pool_max, created_by)
292
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
293
+ RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`;
294
+ var UPDATE_FIELDS = ["name", "type", "host", "port", "database", "username", "ssl", "pool_min", "pool_max"];
270
295
  var DELETE2 = `
271
296
  DELETE FROM data_sources
272
297
  WHERE id = ?`;
273
298
 
274
299
  // src/datasources/datasources.model.ts
275
300
  function listDataSources() {
276
- return getDb2().prepare(LIST2).all();
301
+ return getDb().query(LIST2);
277
302
  }
278
303
  function getDataSourceById(id) {
279
- return getDb2().prepare(FIND_BY_ID2).get(id);
304
+ return getDb().queryOne(FIND_BY_ID2, [id]);
280
305
  }
281
306
  function getDataSourceRow(id) {
282
- return getDb2().prepare(FIND_WITH_PASSWORD).get(id);
307
+ return getDb().queryOne(FIND_WITH_PASSWORD, [id]);
283
308
  }
284
309
  function insertDataSource(data, encryptedPassword, createdBy) {
285
- return getDb2().prepare(CREATE2).get(
310
+ return getDb().queryOne(CREATE2, [
286
311
  data.name,
287
312
  data.type,
288
313
  data.host,
@@ -290,12 +315,13 @@ function insertDataSource(data, encryptedPassword, createdBy) {
290
315
  data.database,
291
316
  data.username,
292
317
  encryptedPassword,
318
+ data.ssl,
293
319
  data.pool_min,
294
320
  data.pool_max,
295
321
  createdBy
296
- );
322
+ ]);
297
323
  }
298
- function updateDataSource(id, data, encryptedPassword) {
324
+ async function updateDataSource(id, data, encryptedPassword) {
299
325
  const fields = [];
300
326
  const values = [];
301
327
  for (const key of UPDATE_FIELDS) {
@@ -310,13 +336,13 @@ function updateDataSource(id, data, encryptedPassword) {
310
336
  }
311
337
  if (!fields.length) return getDataSourceById(id);
312
338
  values.push(id);
313
- const bound = values;
314
- return getDb2().prepare(
315
- `UPDATE data_sources SET ${fields.join(", ")} WHERE id = ? RETURNING id, name, type, host, port, database, username, pool_min, pool_max, created_by, created_at`
316
- ).get(...bound);
339
+ return getDb().queryOne(
340
+ `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`,
341
+ values
342
+ );
317
343
  }
318
344
  function removeDataSource(id) {
319
- return getDb2().prepare(DELETE2).run(id);
345
+ return getDb().run(DELETE2, [id]);
320
346
  }
321
347
 
322
348
  // src/datasources/datasources.service.ts
@@ -341,14 +367,15 @@ function getPool(ds) {
341
367
  username: ds.username,
342
368
  password: ds.password,
343
369
  poolMin: ds.pool_min,
344
- poolMax: ds.pool_max
370
+ poolMax: ds.pool_max,
371
+ ssl: ds.ssl
345
372
  });
346
373
  pools.set(key, pool);
347
374
  logger.info("Connection pool created (ds=%s, db=%s, type=%s)", ds.id, ds.database, ds.type);
348
375
  return pool;
349
376
  }
350
- function getPoolForDatabase(dataSourceId, database) {
351
- const ds = getDataSourceWithPassword(dataSourceId);
377
+ async function getPoolForDatabase(dataSourceId, database) {
378
+ const ds = await getDataSourceWithPassword(dataSourceId);
352
379
  if (!ds) return null;
353
380
  return getPool({ ...ds, database });
354
381
  }
@@ -362,8 +389,8 @@ async function destroyPool(id) {
362
389
  }
363
390
  }
364
391
  }
365
- function getDataSourceWithPassword(id) {
366
- const row = getDataSourceRow(id);
392
+ async function getDataSourceWithPassword(id) {
393
+ const row = await getDataSourceRow(id);
367
394
  if (!row) return null;
368
395
  return {
369
396
  id: row.id,
@@ -373,23 +400,24 @@ function getDataSourceWithPassword(id) {
373
400
  database: row.database ?? null,
374
401
  username: row.username,
375
402
  password: decrypt(row.password_encrypted, config.encryptionKey),
403
+ ssl: row.ssl,
376
404
  pool_min: row.pool_min,
377
405
  pool_max: row.pool_max
378
406
  };
379
407
  }
380
- function datasourceList(_req, reply) {
381
- return reply.send(listDataSources());
408
+ async function datasourceList(_req, reply) {
409
+ return reply.send(await listDataSources());
382
410
  }
383
- function datasourceGet(req, reply) {
411
+ async function datasourceGet(req, reply) {
384
412
  const { id } = req.body ?? {};
385
- const ds = getDataSourceById(id);
413
+ const ds = await getDataSourceById(id);
386
414
  if (!ds) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
387
415
  return reply.send(ds);
388
416
  }
389
- function datasourceCreate(req, reply) {
417
+ async function datasourceCreate(req, reply) {
390
418
  const body = req.body ?? {};
391
- const ds = insertDataSource(
392
- { ...body, pool_min: body.pool_min ?? 1, pool_max: body.pool_max ?? 10 },
419
+ const ds = await insertDataSource(
420
+ { ...body, ssl: body.ssl ?? false, pool_min: body.pool_min ?? 1, pool_max: body.pool_max ?? 10 },
393
421
  encrypt(body.password, config.encryptionKey),
394
422
  req.user.id
395
423
  );
@@ -397,17 +425,17 @@ function datasourceCreate(req, reply) {
397
425
  }
398
426
  async function datasourceUpdate(req, reply) {
399
427
  const { id, password, ...rest } = req.body ?? {};
400
- const existing = getDataSourceById(id);
428
+ const existing = await getDataSourceById(id);
401
429
  if (!existing) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
402
430
  const encryptedPassword = password ? encrypt(password, config.encryptionKey) : void 0;
403
431
  await destroyPool(id);
404
- const ds = updateDataSource(id, rest, encryptedPassword);
432
+ const ds = await updateDataSource(id, rest, encryptedPassword);
405
433
  return reply.send(ds);
406
434
  }
407
435
  async function datasourceDelete(req, reply) {
408
436
  const { id } = req.body ?? {};
409
437
  await destroyPool(id);
410
- removeDataSource(id);
438
+ await removeDataSource(id);
411
439
  return reply.code(204).send();
412
440
  }
413
441
 
@@ -423,9 +451,6 @@ function datasourceController(app) {
423
451
  // src/sql/sql.service.ts
424
452
  import NodeSqlParser from "node-sql-parser";
425
453
 
426
- // src/audit/audit.model.ts
427
- import { getSqlite as getDb3 } from "@heyhru/server-util-sqlite";
428
-
429
454
  // src/audit/audit.sql.ts
430
455
  var INSERT = `
431
456
  INSERT INTO audit_logs (user_id, data_source_id, action, sql_text, result_summary, ip_address)
@@ -438,16 +463,16 @@ WHERE 1=1`;
438
463
 
439
464
  // src/audit/audit.model.ts
440
465
  function insertAuditLog(entry) {
441
- return getDb3().prepare(INSERT).run(
466
+ return getDb().run(INSERT, [
442
467
  entry.userId,
443
468
  entry.dataSourceId ?? null,
444
469
  entry.action,
445
470
  entry.sqlText ?? null,
446
471
  entry.resultSummary ?? null,
447
472
  entry.ipAddress ?? null
448
- );
473
+ ]);
449
474
  }
450
- function queryAuditLogs(filters) {
475
+ async function queryAuditLogs(filters) {
451
476
  let query = LIST3;
452
477
  const params = [];
453
478
  if (filters?.userId) {
@@ -460,8 +485,7 @@ function queryAuditLogs(filters) {
460
485
  }
461
486
  query += " ORDER BY al.created_at DESC LIMIT ? OFFSET ?";
462
487
  params.push(filters?.limit ?? 50, filters?.offset ?? 0);
463
- const bound = params;
464
- return getDb3().prepare(query).all(...bound);
488
+ return getDb().query(query, params);
465
489
  }
466
490
 
467
491
  // src/audit/audit.service.ts
@@ -516,8 +540,8 @@ async function sqlExecute({ dataSourceId, database, sql, userId, ip }) {
516
540
  if (category !== "select") {
517
541
  throw new Error("\u4EC5\u5141\u8BB8\u76F4\u63A5\u6267\u884C SELECT \u8BED\u53E5");
518
542
  }
519
- const pool = database ? getPoolForDatabase(dataSourceId, database) : (() => {
520
- const ds = getDataSourceWithPassword(dataSourceId);
543
+ const pool = database ? await getPoolForDatabase(dataSourceId, database) : await (async () => {
544
+ const ds = await getDataSourceWithPassword(dataSourceId);
521
545
  return ds ? getPool(ds) : null;
522
546
  })();
523
547
  if (!pool) throw new Error("\u6570\u636E\u6E90\u672A\u627E\u5230");
@@ -532,46 +556,48 @@ async function sqlExecute({ dataSourceId, database, sql, userId, ip }) {
532
556
  });
533
557
  return rows;
534
558
  }
535
- function postSqlDatabases(req, reply) {
559
+ async function postSqlDatabases(req, reply) {
536
560
  const { dataSourceId } = req.body ?? {};
537
561
  if (!dataSourceId) {
538
562
  return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u4E0D\u80FD\u4E3A\u7A7A" });
539
563
  }
540
- const ds = getDataSourceWithPassword(dataSourceId);
564
+ const ds = await getDataSourceWithPassword(dataSourceId);
541
565
  if (!ds) return reply.code(404).send({ error: "\u6570\u636E\u6E90\u672A\u627E\u5230" });
542
566
  const pool = getPool({
543
567
  ...ds,
544
568
  database: ds.database ?? (ds.type === "postgres" ? "postgres" : "")
545
569
  });
546
570
  const sql = ds.type === "mysql" ? "SHOW DATABASES" : "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname";
547
- return pool.execute(sql).then((rows) => {
571
+ try {
572
+ const rows = await pool.execute(sql);
548
573
  const names = rows.map(
549
574
  (r) => Object.values(r)[0]
550
575
  );
551
576
  return reply.send(names);
552
- }).catch((err) => {
577
+ } catch (err) {
553
578
  logger.error(err, "Failed to list databases (ds=%s)", dataSourceId);
554
579
  return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
555
- });
580
+ }
556
581
  }
557
- function postSqlTables(req, reply) {
582
+ async function postSqlTables(req, reply) {
558
583
  const { dataSourceId, database } = req.body ?? {};
559
584
  if (!dataSourceId || !database) {
560
585
  return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u548C\u6570\u636E\u5E93\u540D\u4E0D\u80FD\u4E3A\u7A7A" });
561
586
  }
562
- const pool = getPoolForDatabase(dataSourceId, database);
587
+ const pool = await getPoolForDatabase(dataSourceId, database);
563
588
  if (!pool) return reply.code(404).send({ error: "\u6570\u636E\u6E90\u672A\u627E\u5230" });
564
- const ds = getDataSourceWithPassword(dataSourceId);
589
+ const ds = await getDataSourceWithPassword(dataSourceId);
565
590
  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";
566
- return pool.execute(sql).then((rows) => {
591
+ try {
592
+ const rows = await pool.execute(sql);
567
593
  const names = rows.map(
568
594
  (r) => Object.values(r)[0]
569
595
  );
570
596
  return reply.send(names);
571
- }).catch((err) => {
597
+ } catch (err) {
572
598
  logger.error(err, "Failed to list tables (ds=%s, db=%s)", dataSourceId, database);
573
599
  return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
574
- });
600
+ }
575
601
  }
576
602
 
577
603
  // src/sql/sql.controller.ts
@@ -582,9 +608,6 @@ function sqlController(app) {
582
608
  app.post("/sql/tables", postSqlTables);
583
609
  }
584
610
 
585
- // src/approvals/approvals.model.ts
586
- import { getSqlite as getDb4 } from "@heyhru/server-util-sqlite";
587
-
588
611
  // src/approvals/approvals.sql.ts
589
612
  var FIND_BY_ID3 = `
590
613
  SELECT *
@@ -594,22 +617,22 @@ var CREATE3 = `
594
617
  INSERT INTO approvals (data_source_id, sql_text, submitted_by)
595
618
  VALUES (?, ?, ?)
596
619
  RETURNING *`;
597
- var UPDATE_REVIEW = `
620
+ var UPDATE_REVIEW = () => `
598
621
  UPDATE approvals
599
- SET status = ?, reviewed_by = ?, reject_reason = ?, updated_at = datetime('now')
622
+ SET status = ?, reviewed_by = ?, reject_reason = ?, updated_at = ${nowExpr()}
600
623
  WHERE id = ?
601
624
  RETURNING *`;
602
- var UPDATE_EXECUTING = `
625
+ var UPDATE_EXECUTING = () => `
603
626
  UPDATE approvals
604
- SET status = 'executing', updated_at = datetime('now')
627
+ SET status = 'executing', updated_at = ${nowExpr()}
605
628
  WHERE id = ?`;
606
- var UPDATE_RESULT = `
629
+ var UPDATE_RESULT = () => `
607
630
  UPDATE approvals
608
- SET status = ?, execute_result = ?, updated_at = datetime('now')
631
+ SET status = ?, execute_result = ?, updated_at = ${nowExpr()}
609
632
  WHERE id = ?`;
610
633
 
611
634
  // src/approvals/approvals.model.ts
612
- function listApprovals(filters) {
635
+ async function listApprovals(filters) {
613
636
  let query = "SELECT * FROM approvals WHERE 1=1";
614
637
  const params = [];
615
638
  if (filters?.status) {
@@ -621,52 +644,51 @@ function listApprovals(filters) {
621
644
  params.push(filters.submittedBy);
622
645
  }
623
646
  query += " ORDER BY created_at DESC";
624
- const bound = params;
625
- return getDb4().prepare(query).all(...bound);
647
+ return getDb().query(query, params);
626
648
  }
627
649
  function getApprovalById(id) {
628
- return getDb4().prepare(FIND_BY_ID3).get(id);
650
+ return getDb().queryOne(FIND_BY_ID3, [id]);
629
651
  }
630
652
  function insertApproval(dataSourceId, sqlText, submittedBy) {
631
- return getDb4().prepare(CREATE3).get(dataSourceId, sqlText, submittedBy);
653
+ return getDb().queryOne(CREATE3, [dataSourceId, sqlText, submittedBy]);
632
654
  }
633
655
  function updateReview(id, status, reviewedBy, rejectReason) {
634
- return getDb4().prepare(UPDATE_REVIEW).get(status, reviewedBy, rejectReason, id);
656
+ return getDb().queryOne(UPDATE_REVIEW(), [status, reviewedBy, rejectReason, id]);
635
657
  }
636
658
  function setExecuting(id) {
637
- return getDb4().prepare(UPDATE_EXECUTING).run(id);
659
+ return getDb().run(UPDATE_EXECUTING(), [id]);
638
660
  }
639
661
  function setExecuteResult(id, status, result) {
640
- return getDb4().prepare(UPDATE_RESULT).run(status, result, id);
662
+ return getDb().run(UPDATE_RESULT(), [status, result, id]);
641
663
  }
642
664
 
643
665
  // src/approvals/approvals.service.ts
644
- function approvalList(req, reply) {
666
+ async function approvalList(req, reply) {
645
667
  const { status, mine } = req.body ?? {};
646
668
  return reply.send(
647
- listApprovals({
669
+ await listApprovals({
648
670
  status,
649
671
  submittedBy: mine === "true" ? req.user.id : void 0
650
672
  })
651
673
  );
652
674
  }
653
- function approvalGet(req, reply) {
675
+ async function approvalGet(req, reply) {
654
676
  const { id } = req.body ?? {};
655
- const approval = getApprovalById(id);
677
+ const approval = await getApprovalById(id);
656
678
  if (!approval) return reply.code(404).send({ error: "\u672A\u627E\u5230" });
657
679
  return reply.send(approval);
658
680
  }
659
- function approvalCreate(req, reply) {
681
+ async function approvalCreate(req, reply) {
660
682
  const { dataSourceId, sql } = req.body ?? {};
661
683
  if (!dataSourceId || !sql) {
662
684
  return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u548C SQL \u4E0D\u80FD\u4E3A\u7A7A" });
663
685
  }
664
- const approval = insertApproval(dataSourceId, sql, req.user.id);
686
+ const approval = await insertApproval(dataSourceId, sql, req.user.id);
665
687
  logger.info("Approval submitted (user=%s)", req.user.id);
666
688
  return reply.code(201).send(approval);
667
689
  }
668
- function reviewApproval(id, reviewerId, decision, rejectReason) {
669
- const approval = getApprovalById(id);
690
+ async function reviewApproval(id, reviewerId, decision, rejectReason) {
691
+ const approval = await getApprovalById(id);
670
692
  if (!approval || approval["status"] !== "pending") {
671
693
  throw new Error("\u5BA1\u6279\u8BB0\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u5728\u5F85\u5BA1\u6279\u72B6\u6001");
672
694
  }
@@ -675,10 +697,10 @@ function reviewApproval(id, reviewerId, decision, rejectReason) {
675
697
  }
676
698
  return updateReview(id, decision, reviewerId, rejectReason ?? null);
677
699
  }
678
- function approvalApprove(req, reply) {
700
+ async function approvalApprove(req, reply) {
679
701
  const { id } = req.body ?? {};
680
702
  try {
681
- const result = reviewApproval(id, req.user.id, "approved");
703
+ const result = await reviewApproval(id, req.user.id, "approved");
682
704
  logger.info("Approval approved (id=%s, reviewer=%s)", id, req.user.id);
683
705
  return reply.send(result);
684
706
  } catch (err) {
@@ -686,10 +708,10 @@ function approvalApprove(req, reply) {
686
708
  return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
687
709
  }
688
710
  }
689
- function approvalReject(req, reply) {
711
+ async function approvalReject(req, reply) {
690
712
  const { id, reason } = req.body ?? {};
691
713
  try {
692
- const result = reviewApproval(id, req.user.id, "rejected", reason);
714
+ const result = await reviewApproval(id, req.user.id, "rejected", reason);
693
715
  logger.info("Approval rejected (id=%s, reviewer=%s)", id, req.user.id);
694
716
  return reply.send(result);
695
717
  } catch (err) {
@@ -709,18 +731,18 @@ async function approvalExecute(req, reply) {
709
731
  }
710
732
  }
711
733
  async function doExecuteApproval(id, userId, ip) {
712
- const approval = getApprovalById(id);
734
+ const approval = await getApprovalById(id);
713
735
  if (!approval || approval["status"] !== "approved") {
714
736
  throw new Error("\u5BA1\u6279\u8BB0\u5F55\u4E0D\u5B58\u5728\u6216\u672A\u901A\u8FC7\u5BA1\u6279");
715
737
  }
716
- setExecuting(id);
738
+ await setExecuting(id);
717
739
  try {
718
- const ds = getDataSourceWithPassword(approval["data_source_id"]);
740
+ const ds = await getDataSourceWithPassword(approval["data_source_id"]);
719
741
  if (!ds) throw new Error("\u6570\u636E\u6E90\u672A\u627E\u5230");
720
742
  const pool = getPool(ds);
721
743
  const rows = await pool.execute(approval["sql_text"]);
722
744
  const result = `${rows.length} rows affected`;
723
- setExecuteResult(id, "executed", result);
745
+ await setExecuteResult(id, "executed", result);
724
746
  logger.info("Approval executed (id=%s, user=%s)", id, userId);
725
747
  await writeAuditLog({
726
748
  userId,
@@ -733,7 +755,7 @@ async function doExecuteApproval(id, userId, ip) {
733
755
  return getApprovalById(id);
734
756
  } catch (err) {
735
757
  const message = err instanceof Error ? err.message : String(err);
736
- setExecuteResult(id, "execute_failed", message);
758
+ await setExecuteResult(id, "execute_failed", message);
737
759
  logger.error(err, "Approval execute_failed (id=%s)", id);
738
760
  throw err;
739
761
  }
@@ -774,38 +796,39 @@ async function buildApp() {
774
796
  return app;
775
797
  }
776
798
 
777
- // src/migrate/runner.ts
778
- import { getSqlite as getDb5 } from "@heyhru/server-util-sqlite";
779
-
780
799
  // src/migrate/migrations.ts
800
+ var idCol = (driver) => driver === "postgres" ? "id UUID PRIMARY KEY DEFAULT gen_random_uuid()" : "id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16))))";
801
+ var tsCol = (name, driver) => driver === "postgres" ? `${name} TIMESTAMPTZ NOT NULL DEFAULT NOW()` : `${name} TEXT NOT NULL DEFAULT (datetime('now'))`;
802
+ var fkNotNull = (name, ref, driver) => driver === "postgres" ? `${name} UUID NOT NULL REFERENCES ${ref}(id)` : `${name} TEXT NOT NULL REFERENCES ${ref}(id)`;
803
+ var fkNullable = (name, ref, driver) => driver === "postgres" ? `${name} UUID REFERENCES ${ref}(id)` : `${name} TEXT REFERENCES ${ref}(id)`;
781
804
  var migrations = [
782
805
  {
783
806
  name: "001_users.sql",
784
- sql: `
807
+ sql: (d) => `
785
808
  CREATE TABLE IF NOT EXISTS users (
786
- id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
809
+ ${idCol(d)},
787
810
  username TEXT NOT NULL UNIQUE,
788
811
  email TEXT NOT NULL UNIQUE,
789
812
  password_hash TEXT NOT NULL,
790
813
  role TEXT NOT NULL CHECK (role IN ('admin', 'maintainer', 'developer', 'viewer')),
791
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
792
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
814
+ ${tsCol("created_at", d)},
815
+ ${tsCol("updated_at", d)}
793
816
  );
794
817
 
795
- INSERT OR IGNORE INTO users (id, username, email, password_hash, role)
818
+ INSERT ${d === "postgres" ? "" : "OR IGNORE "}INTO users (id, username, email, password_hash, role)
796
819
  VALUES (
797
820
  'admin-seed-id-0000000000000000',
798
821
  'admin',
799
822
  'admin@example.com',
800
823
  '178c20236d9629bffcb301f57d1b8383:40c49d6500a8f322754ac0cd2d5c9dea019a4c14feef60e436d767cc2dd44bc1eeee7ef16f1bd768260aeec4025e06c479c4c367537899002ec89962382a3104',
801
824
  'admin'
802
- );`
825
+ )${d === "postgres" ? " ON CONFLICT (id) DO NOTHING" : ""};`
803
826
  },
804
827
  {
805
828
  name: "002_data_sources.sql",
806
- sql: `
829
+ sql: (d) => `
807
830
  CREATE TABLE IF NOT EXISTS data_sources (
808
- id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
831
+ ${idCol(d)},
809
832
  name TEXT NOT NULL UNIQUE,
810
833
  type TEXT NOT NULL CHECK (type IN ('mysql', 'postgres')),
811
834
  host TEXT NOT NULL,
@@ -813,77 +836,84 @@ CREATE TABLE IF NOT EXISTS data_sources (
813
836
  database TEXT,
814
837
  username TEXT NOT NULL,
815
838
  password_encrypted TEXT NOT NULL,
839
+ ssl BOOLEAN NOT NULL DEFAULT FALSE,
816
840
  pool_min INTEGER NOT NULL DEFAULT 1,
817
841
  pool_max INTEGER NOT NULL DEFAULT 10,
818
- created_by TEXT NOT NULL REFERENCES users(id),
819
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
820
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
842
+ ${fkNotNull("created_by", "users", d)},
843
+ ${tsCol("created_at", d)},
844
+ ${tsCol("updated_at", d)}
821
845
  );`
822
846
  },
823
847
  {
824
848
  name: "003_audit_logs.sql",
825
- sql: `
849
+ sql: (d) => `
826
850
  CREATE TABLE IF NOT EXISTS audit_logs (
827
- id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
828
- user_id TEXT NOT NULL REFERENCES users(id),
829
- data_source_id TEXT REFERENCES data_sources(id),
851
+ ${idCol(d)},
852
+ ${fkNotNull("user_id", "users", d)},
853
+ ${fkNullable("data_source_id", "data_sources", d)},
830
854
  action TEXT NOT NULL,
831
855
  sql_text TEXT,
832
856
  result_summary TEXT,
833
857
  ip_address TEXT,
834
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
858
+ ${tsCol("created_at", d)}
835
859
  );
836
860
 
837
861
  CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
862
+ CREATE INDEX IF NOT EXISTS idx_audit_logs_data_source_id ON audit_logs(data_source_id);
838
863
  CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);`
839
864
  },
840
865
  {
841
866
  name: "004_approvals.sql",
842
- sql: `
867
+ sql: (d) => `
843
868
  CREATE TABLE IF NOT EXISTS approvals (
844
- id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
845
- data_source_id TEXT NOT NULL REFERENCES data_sources(id),
846
- submitted_by TEXT NOT NULL REFERENCES users(id),
847
- reviewed_by TEXT REFERENCES users(id),
869
+ ${idCol(d)},
870
+ ${fkNotNull("data_source_id", "data_sources", d)},
871
+ ${fkNotNull("submitted_by", "users", d)},
872
+ ${fkNullable("reviewed_by", "users", d)},
848
873
  sql_text TEXT NOT NULL,
849
874
  status TEXT NOT NULL DEFAULT 'pending'
850
875
  CHECK (status IN ('pending', 'approved', 'rejected', 'executing', 'executed', 'execute_failed')),
851
876
  reject_reason TEXT,
852
877
  execute_result TEXT,
853
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
854
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
878
+ ${tsCol("created_at", d)},
879
+ ${tsCol("updated_at", d)}
855
880
  );
856
881
 
857
882
  CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status);
858
883
  CREATE INDEX IF NOT EXISTS idx_approvals_submitted_by ON approvals(submitted_by);`
884
+ },
885
+ {
886
+ name: "005_data_sources_add_ssl.sql",
887
+ sql: () => `
888
+ ALTER TABLE data_sources ADD COLUMN ssl BOOLEAN NOT NULL DEFAULT FALSE;`
859
889
  }
860
890
  ];
861
891
 
862
892
  // src/migrate/runner.ts
863
- function runMigrations() {
864
- const db = getDb5();
865
- db.exec(`
866
- CREATE TABLE IF NOT EXISTS _migrations (
867
- name TEXT PRIMARY KEY,
868
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
869
- )
870
- `);
871
- const applied = new Set(
872
- db.prepare("SELECT name FROM _migrations").all().map((r) => r.name)
873
- );
893
+ async function runMigrations() {
894
+ const db = getDb();
895
+ const driver = getDriver();
896
+ const createMigrationTable = driver === "postgres" ? `CREATE TABLE IF NOT EXISTS _migrations (
897
+ name TEXT PRIMARY KEY,
898
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
899
+ )` : `CREATE TABLE IF NOT EXISTS _migrations (
900
+ name TEXT PRIMARY KEY,
901
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
902
+ )`;
903
+ await db.exec(createMigrationTable);
904
+ const rows = await db.query("SELECT name FROM _migrations");
905
+ const applied = new Set(rows.map((r) => r.name));
874
906
  for (const migration of migrations) {
875
907
  if (applied.has(migration.name)) continue;
876
- db.exec(migration.sql);
877
- db.prepare("INSERT INTO _migrations (name) VALUES (?)").run(
878
- migration.name
879
- );
908
+ await db.exec(migration.sql(driver));
909
+ await db.run("INSERT INTO _migrations (name) VALUES (?)", [migration.name]);
880
910
  logger.info("Migration applied: %s", migration.name);
881
911
  }
882
912
  }
883
913
 
884
914
  // src/index.ts
885
915
  async function main() {
886
- createSqlite({ path: config.dbUrl.replace("sqlite://", "") });
916
+ await createDmsDb(config.dbUrl);
887
917
  await runMigrations();
888
918
  const app = await buildApp();
889
919
  await app.listen({ port: config.port, host: "0.0.0.0" });