admindb 2.4.1 → 2.6.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/LICENSE +20 -20
- package/dist/app.js +48 -25
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.js +2 -0
- package/dist/core/diff/data-differ.d.ts +1 -0
- package/dist/core/diff/data-differ.js +243 -0
- package/dist/core/diff/patch-generator.d.ts +1 -0
- package/dist/core/diff/patch-generator.js +77 -0
- package/dist/core/diff/schema-differ.d.ts +1 -0
- package/dist/core/diff/schema-differ.js +229 -0
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +39 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +20 -0
- package/dist/core/result.d.ts +1 -0
- package/dist/core/result.js +10 -0
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +37 -0
- package/dist/core/transfer/dialect-mapper.d.ts +1 -0
- package/dist/core/transfer/dialect-mapper.js +303 -0
- package/dist/core/transfer/masking.d.ts +1 -0
- package/dist/core/transfer/masking.js +49 -0
- package/dist/core/transfer/sync-engine.d.ts +1 -0
- package/dist/core/transfer/sync-engine.js +234 -0
- package/dist/core/transfer/topological-sort.d.ts +1 -0
- package/dist/core/transfer/topological-sort.js +55 -0
- package/dist/data/chain.js +1 -1
- package/dist/data/engine.js +1 -1
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.js +9 -31
- package/dist/db/dialects/index.d.ts +1 -0
- package/dist/db/dialects/index.js +58 -0
- package/dist/db/dialects/postgres/ddl.d.ts +1 -0
- package/dist/db/dialects/postgres/ddl.js +107 -0
- package/dist/db/dialects/postgres/dialect.d.ts +1 -0
- package/dist/db/dialects/postgres/dialect.js +28 -0
- package/dist/db/dialects/postgres/introspector.d.ts +1 -0
- package/dist/db/dialects/postgres/introspector.js +265 -0
- package/dist/db/dialects/postgres/types.d.ts +1 -0
- package/dist/db/dialects/postgres/types.js +83 -0
- package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
- package/dist/db/dialects/sqlite/ddl.js +244 -0
- package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
- package/dist/db/dialects/sqlite/dialect.js +25 -0
- package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
- package/dist/db/dialects/sqlite/introspector.js +217 -0
- package/dist/db/dialects/sqlite/types.d.ts +1 -0
- package/dist/db/dialects/sqlite/types.js +69 -0
- package/dist/db/dialects/types.d.ts +1 -0
- package/dist/db/dialects/types.js +15 -0
- package/dist/db/export.js +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/manager.d.ts +1 -1
- package/dist/db/manager.js +101 -21
- package/dist/db/postgres.d.ts +1 -1
- package/dist/db/postgres.js +20 -284
- package/dist/db/types.d.ts +1 -1
- package/dist/db/types.js +0 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/modules/databases/databases.routes.d.ts +1 -0
- package/dist/modules/databases/databases.routes.js +150 -0
- package/dist/modules/databases/databases.service.d.ts +1 -0
- package/dist/modules/databases/databases.service.js +134 -0
- package/dist/modules/databases/index.d.ts +1 -0
- package/dist/{routes → modules/databases}/index.js +2 -3
- package/dist/modules/erd/erd.routes.d.ts +1 -0
- package/dist/modules/erd/erd.routes.js +32 -0
- package/dist/modules/erd/erd.service.d.ts +1 -0
- package/dist/modules/erd/erd.service.js +39 -0
- package/dist/modules/erd/index.d.ts +1 -0
- package/dist/modules/erd/index.js +18 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +70 -0
- package/dist/modules/query/index.d.ts +1 -0
- package/dist/modules/query/index.js +18 -0
- package/dist/modules/query/query.routes.d.ts +1 -0
- package/dist/modules/query/query.routes.js +54 -0
- package/dist/modules/query/query.service.d.ts +1 -0
- package/dist/modules/query/query.service.js +95 -0
- package/dist/modules/rows/blob.helper.d.ts +1 -0
- package/dist/modules/rows/blob.helper.js +40 -0
- package/dist/modules/rows/display.helper.d.ts +1 -0
- package/dist/modules/rows/display.helper.js +73 -0
- package/dist/modules/rows/index.d.ts +1 -0
- package/dist/modules/rows/index.js +20 -0
- package/dist/modules/rows/rows.routes.d.ts +1 -0
- package/dist/modules/rows/rows.routes.js +555 -0
- package/dist/modules/rows/rows.service.d.ts +1 -0
- package/dist/modules/rows/rows.service.js +433 -0
- package/dist/modules/schema/index.d.ts +1 -0
- package/dist/modules/schema/index.js +18 -0
- package/dist/modules/schema/schema.routes.d.ts +1 -0
- package/dist/modules/schema/schema.routes.js +171 -0
- package/dist/modules/schema/schema.service.d.ts +1 -0
- package/dist/modules/schema/schema.service.js +58 -0
- package/dist/modules/search/index.d.ts +1 -0
- package/dist/modules/search/index.js +19 -0
- package/dist/modules/search/search.routes.d.ts +1 -0
- package/dist/modules/search/search.routes.js +38 -0
- package/dist/modules/search/search.service.d.ts +1 -0
- package/dist/modules/search/search.service.js +458 -0
- package/dist/modules/search/search.types.d.ts +1 -0
- package/dist/modules/search/search.types.js +2 -0
- package/dist/modules/seed/index.d.ts +1 -0
- package/dist/modules/seed/index.js +18 -0
- package/dist/modules/seed/seed.routes.d.ts +1 -0
- package/dist/modules/seed/seed.routes.js +212 -0
- package/dist/modules/seed/seed.service.d.ts +1 -0
- package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
- package/dist/modules/sync/index.d.ts +1 -0
- package/dist/modules/sync/index.js +18 -0
- package/dist/modules/sync/sync.routes.d.ts +1 -0
- package/dist/modules/sync/sync.routes.js +135 -0
- package/dist/modules/sync/sync.service.d.ts +1 -0
- package/dist/modules/sync/sync.service.js +41 -0
- package/dist/modules/tables/index.d.ts +1 -0
- package/dist/modules/tables/index.js +18 -0
- package/dist/modules/tables/tables.routes.d.ts +1 -0
- package/dist/modules/tables/tables.routes.js +194 -0
- package/dist/modules/tables/tables.service.d.ts +1 -0
- package/dist/modules/tables/tables.service.js +218 -0
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/search.js +1 -0
- package/dist/public/js/sync.js +1 -0
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +1 -3
- package/dist/utils/icons.js +3 -0
- package/dist/utils/stream.d.ts +1 -0
- package/dist/utils/stream.js +131 -0
- package/dist/views/layouts/main.hbs +1 -1
- package/dist/views/pages/error.hbs +10 -1
- package/dist/views/pages/home.hbs +1 -1
- package/dist/views/pages/query.hbs +1 -1
- package/dist/views/pages/seed-select.hbs +1 -252
- package/dist/views/pages/seed.hbs +1 -307
- package/dist/views/pages/sync.hbs +409 -0
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/navbar.hbs +1 -1
- package/dist/views/partials/sidebar.hbs +1 -1
- package/docs/API.md +4 -17
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +10 -9
- package/dist/db/introspection.d.ts +0 -1
- package/dist/db/introspection.js +0 -164
- package/dist/routes/api/erd.d.ts +0 -1
- package/dist/routes/api/erd.js +0 -45
- package/dist/routes/api/helpers.d.ts +0 -1
- package/dist/routes/api/import-export.d.ts +0 -1
- package/dist/routes/api/import-export.js +0 -84
- package/dist/routes/api/index.d.ts +0 -1
- package/dist/routes/api/index.js +0 -55
- package/dist/routes/api/query.d.ts +0 -1
- package/dist/routes/api/query.js +0 -77
- package/dist/routes/api/rows.d.ts +0 -1
- package/dist/routes/api/rows.js +0 -373
- package/dist/routes/api/seed.d.ts +0 -1
- package/dist/routes/api/seed.js +0 -180
- package/dist/routes/api/tables.d.ts +0 -1
- package/dist/routes/api/tables.js +0 -230
- package/dist/routes/databases.d.ts +0 -1
- package/dist/routes/databases.js +0 -212
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/pages.d.ts +0 -1
- package/dist/routes/pages.js +0 -540
package/dist/db/postgres.js
CHANGED
|
@@ -4,8 +4,8 @@ exports.PostgresDatabase = void 0;
|
|
|
4
4
|
const pg_1 = require("pg");
|
|
5
5
|
const generator_1 = require("../sql/generator");
|
|
6
6
|
const common_1 = require("../utils/common");
|
|
7
|
-
const types_1 = require("./types");
|
|
8
7
|
const filters_1 = require("./filters");
|
|
8
|
+
const index_1 = require("./dialects/index");
|
|
9
9
|
class PostgresDatabase {
|
|
10
10
|
pool;
|
|
11
11
|
logger;
|
|
@@ -13,6 +13,7 @@ class PostgresDatabase {
|
|
|
13
13
|
isReadOnly;
|
|
14
14
|
dialect = 'postgres';
|
|
15
15
|
schema;
|
|
16
|
+
dialectInstance;
|
|
16
17
|
constructor(connection, logger, options = {}) {
|
|
17
18
|
this.logger = logger;
|
|
18
19
|
const opts = typeof connection === 'string'
|
|
@@ -20,6 +21,7 @@ class PostgresDatabase {
|
|
|
20
21
|
: { ...connection, ...options };
|
|
21
22
|
this.isReadOnly = Boolean(opts.readonly);
|
|
22
23
|
this.schema = opts.schema || 'public';
|
|
24
|
+
this.dialectInstance = (0, index_1.getDialect)('postgres', this.schema);
|
|
23
25
|
let sslConfig = opts.ssl;
|
|
24
26
|
if (opts.connectionString) {
|
|
25
27
|
try {
|
|
@@ -62,11 +64,6 @@ class PostgresDatabase {
|
|
|
62
64
|
this.pool.on('error', (err) => {
|
|
63
65
|
this.logger.error(`Unexpected PostgreSQL client error: ${(0, common_1.errorMessage)(err)}`);
|
|
64
66
|
});
|
|
65
|
-
if (!this.isReadOnly) {
|
|
66
|
-
this.initSchema().catch((err) => {
|
|
67
|
-
this.logger.warn(`Failed to initialize Postgres schema: ${(0, common_1.errorMessage)(err)}`);
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
67
|
this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly ? ' (read-only)' : ''}`);
|
|
71
68
|
}
|
|
72
69
|
readonlyBlocked() {
|
|
@@ -113,16 +110,6 @@ class PostgresDatabase {
|
|
|
113
110
|
client.release();
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
|
-
async initSchema() {
|
|
117
|
-
await this.pool.query(`
|
|
118
|
-
CREATE TABLE IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (
|
|
119
|
-
id SERIAL PRIMARY KEY,
|
|
120
|
-
name VARCHAR(255) NOT NULL UNIQUE,
|
|
121
|
-
sql TEXT NOT NULL,
|
|
122
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
123
|
-
);
|
|
124
|
-
`);
|
|
125
|
-
}
|
|
126
113
|
hasMultipleStatements(sql) {
|
|
127
114
|
const s = String(sql ?? '').trim();
|
|
128
115
|
if (!s)
|
|
@@ -173,121 +160,10 @@ class PostgresDatabase {
|
|
|
173
160
|
return this.execResult(sql);
|
|
174
161
|
}
|
|
175
162
|
async listTables() {
|
|
176
|
-
return this.
|
|
177
|
-
const res = await this.pool.query(`SELECT table_name AS name
|
|
178
|
-
FROM information_schema.tables
|
|
179
|
-
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
|
|
180
|
-
ORDER BY table_name;`, [this.schema]);
|
|
181
|
-
return res.rows.map((r) => ({ name: r.name }));
|
|
182
|
-
});
|
|
163
|
+
return this.dialectInstance.introspector.listTables(this.pool);
|
|
183
164
|
}
|
|
184
165
|
async getTableInfo(table) {
|
|
185
|
-
return this.
|
|
186
|
-
const [colRes, pkRes, fkRes, idxRes] = await Promise.all([
|
|
187
|
-
this.pool.query(`SELECT
|
|
188
|
-
ordinal_position AS cid,
|
|
189
|
-
column_name AS name,
|
|
190
|
-
data_type AS type,
|
|
191
|
-
udt_name,
|
|
192
|
-
CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,
|
|
193
|
-
column_default AS dflt_value
|
|
194
|
-
FROM information_schema.columns
|
|
195
|
-
WHERE table_schema = $1 AND table_name = $2
|
|
196
|
-
ORDER BY ordinal_position;`, [this.schema, table]),
|
|
197
|
-
this.pool.query(`SELECT kcu.column_name
|
|
198
|
-
FROM information_schema.table_constraints tc
|
|
199
|
-
JOIN information_schema.key_column_usage kcu
|
|
200
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
201
|
-
AND tc.table_schema = kcu.table_schema
|
|
202
|
-
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
203
|
-
AND tc.table_schema = $1
|
|
204
|
-
AND tc.table_name = $2
|
|
205
|
-
ORDER BY kcu.ordinal_position;`, [this.schema, table]),
|
|
206
|
-
this.pool.query(`SELECT
|
|
207
|
-
row_number() OVER () AS id,
|
|
208
|
-
kcu.position_in_unique_constraint AS seq,
|
|
209
|
-
ccu.table_name AS table,
|
|
210
|
-
kcu.column_name AS "from",
|
|
211
|
-
ccu.column_name AS "to",
|
|
212
|
-
rc.update_rule AS on_update,
|
|
213
|
-
rc.delete_rule AS on_delete
|
|
214
|
-
FROM information_schema.table_constraints AS tc
|
|
215
|
-
JOIN information_schema.key_column_usage AS kcu
|
|
216
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
217
|
-
AND tc.table_schema = kcu.table_schema
|
|
218
|
-
JOIN information_schema.constraint_column_usage AS ccu
|
|
219
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
220
|
-
AND ccu.table_schema = tc.table_schema
|
|
221
|
-
JOIN information_schema.referential_constraints AS rc
|
|
222
|
-
ON rc.constraint_name = tc.constraint_name
|
|
223
|
-
AND rc.constraint_schema = tc.table_schema
|
|
224
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
225
|
-
AND tc.table_schema = $1
|
|
226
|
-
AND tc.table_name = $2;`, [this.schema, table]),
|
|
227
|
-
this.pool.query(`SELECT
|
|
228
|
-
i.relname AS name,
|
|
229
|
-
ix.indisunique AS unique,
|
|
230
|
-
pg_get_indexdef(ix.indexrelid) AS sql,
|
|
231
|
-
ARRAY(
|
|
232
|
-
SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)
|
|
233
|
-
FROM generate_subscripts(ix.indkey, 1) as k
|
|
234
|
-
ORDER BY k
|
|
235
|
-
) AS columns
|
|
236
|
-
FROM pg_index ix
|
|
237
|
-
JOIN pg_class t ON t.oid = ix.indrelid
|
|
238
|
-
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
239
|
-
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
240
|
-
WHERE n.nspname = $1 AND t.relname = $2;`, [this.schema, table]),
|
|
241
|
-
]);
|
|
242
|
-
const primaryKey = pkRes.rows.map((r) => r.column_name);
|
|
243
|
-
const columns = colRes.rows.map((c) => {
|
|
244
|
-
const isPk = primaryKey.includes(c.name);
|
|
245
|
-
const displayType = c.type === 'USER-DEFINED' ? c.udt_name : c.type;
|
|
246
|
-
return {
|
|
247
|
-
cid: c.cid,
|
|
248
|
-
name: c.name,
|
|
249
|
-
type: displayType.toUpperCase(),
|
|
250
|
-
notnull: c.notnull,
|
|
251
|
-
dflt_value: c.dflt_value,
|
|
252
|
-
pk: isPk ? primaryKey.indexOf(c.name) + 1 : 0,
|
|
253
|
-
};
|
|
254
|
-
});
|
|
255
|
-
const indexes = idxRes.rows.map((ix) => ({
|
|
256
|
-
name: ix.name,
|
|
257
|
-
unique: Boolean(ix.unique),
|
|
258
|
-
partial: 0,
|
|
259
|
-
origin: 'c',
|
|
260
|
-
columns: Array.isArray(ix.columns) ? ix.columns : [],
|
|
261
|
-
sql: ix.sql,
|
|
262
|
-
}));
|
|
263
|
-
const colDefs = columns.map((c) => {
|
|
264
|
-
let def = ` ${(0, generator_1.quoteIdentifier)(c.name)} ${c.type}`;
|
|
265
|
-
if (c.notnull)
|
|
266
|
-
def += ' NOT NULL';
|
|
267
|
-
if (c.dflt_value)
|
|
268
|
-
def += ` DEFAULT ${c.dflt_value}`;
|
|
269
|
-
return def;
|
|
270
|
-
});
|
|
271
|
-
if (primaryKey.length) {
|
|
272
|
-
colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(', ')})`);
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
table,
|
|
276
|
-
columns,
|
|
277
|
-
foreignKeys: fkRes.rows.map((f) => ({
|
|
278
|
-
id: Number(f.id),
|
|
279
|
-
seq: Number(f.seq ?? 1),
|
|
280
|
-
table: f.table,
|
|
281
|
-
from: f.from,
|
|
282
|
-
to: f.to,
|
|
283
|
-
on_update: f.on_update,
|
|
284
|
-
on_delete: f.on_delete,
|
|
285
|
-
})),
|
|
286
|
-
primaryKey,
|
|
287
|
-
indexes,
|
|
288
|
-
sql: `CREATE TABLE ${(0, generator_1.quoteIdentifier)(table)} (\n${colDefs.join(',\n')}\n);`,
|
|
289
|
-
};
|
|
290
|
-
});
|
|
166
|
+
return this.dialectInstance.introspector.getTableInfo(this.pool, table);
|
|
291
167
|
}
|
|
292
168
|
async getRowCount(table, filters) {
|
|
293
169
|
return this.tryRun(async () => {
|
|
@@ -450,24 +326,6 @@ class PostgresDatabase {
|
|
|
450
326
|
return { success: false, error: info.error };
|
|
451
327
|
return { success: true, data: info.data.sql };
|
|
452
328
|
}
|
|
453
|
-
async listSavedQueries() {
|
|
454
|
-
return this.tryRun(async () => {
|
|
455
|
-
const res = await this.pool.query(`SELECT id, name, sql, created_at FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} ORDER BY name`);
|
|
456
|
-
return res.rows;
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
async saveQuery(name, sql) {
|
|
460
|
-
return this.tryRun(async () => {
|
|
461
|
-
const res = await this.pool.query(`INSERT INTO ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (name, sql) VALUES ($1, $2) RETURNING id`, [name, sql]);
|
|
462
|
-
return { changes: res.rowCount ?? 1, lastInsertRowid: Number(res.rows[0]?.id ?? null) };
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
async deleteSavedQuery(id) {
|
|
466
|
-
return this.tryRun(async () => {
|
|
467
|
-
const res = await this.pool.query(`DELETE FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`, [Number(id)]);
|
|
468
|
-
return { changes: res.rowCount ?? 0, lastInsertRowid: null };
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
329
|
async getSettings() {
|
|
472
330
|
return this.tryRun(async () => {
|
|
473
331
|
const keys = ['server_version', 'max_connections', 'port', 'timezone', 'shared_buffers', 'work_mem'];
|
|
@@ -488,100 +346,16 @@ class PostgresDatabase {
|
|
|
488
346
|
});
|
|
489
347
|
}
|
|
490
348
|
async getSchema(table) {
|
|
491
|
-
return this.
|
|
492
|
-
const infoR = await this.getTableInfo(table);
|
|
493
|
-
if (!infoR.success || !infoR.data)
|
|
494
|
-
throw new Error(infoR.error || `Table ${table} not found.`);
|
|
495
|
-
const { columns, foreignKeys, indexes, primaryKey } = infoR.data;
|
|
496
|
-
const refRes = await this.pool.query(`SELECT
|
|
497
|
-
tc.table_name AS table,
|
|
498
|
-
kcu.column_name AS "from",
|
|
499
|
-
ccu.column_name AS "to"
|
|
500
|
-
FROM information_schema.table_constraints AS tc
|
|
501
|
-
JOIN information_schema.key_column_usage AS kcu
|
|
502
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
503
|
-
AND tc.table_schema = kcu.table_schema
|
|
504
|
-
JOIN information_schema.constraint_column_usage AS ccu
|
|
505
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
506
|
-
AND ccu.table_schema = tc.table_schema
|
|
507
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
508
|
-
AND tc.table_schema = $1
|
|
509
|
-
AND ccu.table_name = $2;`, [this.schema, table]);
|
|
510
|
-
const references = refRes.rows;
|
|
511
|
-
const columnDetails = columns.map((col) => {
|
|
512
|
-
const isPk = primaryKey.includes(col.name);
|
|
513
|
-
const fk = foreignKeys.find((f) => f.from === col.name) ?? null;
|
|
514
|
-
const matchingIndexes = indexes.filter((i) => i.columns.includes(col.name));
|
|
515
|
-
const unique = matchingIndexes.some((i) => i.unique && i.columns.length === 1 && i.columns[0] === col.name);
|
|
516
|
-
const indexed = matchingIndexes.length > 0;
|
|
517
|
-
const referencedBy = references
|
|
518
|
-
.filter((r) => r.to === col.name)
|
|
519
|
-
.map((r) => ({ table: r.table, from: r.from }));
|
|
520
|
-
const dropBlockers = [];
|
|
521
|
-
if (isPk && primaryKey.length === 1)
|
|
522
|
-
dropBlockers.push('Primary key');
|
|
523
|
-
if (referencedBy.length > 0) {
|
|
524
|
-
dropBlockers.push(`Referenced by ${referencedBy.map((r) => `${r.table}.${r.from}`).join(', ')}`);
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
...col,
|
|
528
|
-
unique,
|
|
529
|
-
indexed,
|
|
530
|
-
referencedBy,
|
|
531
|
-
fk,
|
|
532
|
-
canDrop: dropBlockers.length === 0,
|
|
533
|
-
dropBlockers,
|
|
534
|
-
};
|
|
535
|
-
});
|
|
536
|
-
return {
|
|
537
|
-
table,
|
|
538
|
-
columns: columnDetails,
|
|
539
|
-
foreignKeys,
|
|
540
|
-
indexes,
|
|
541
|
-
references,
|
|
542
|
-
};
|
|
543
|
-
});
|
|
349
|
+
return this.dialectInstance.introspector.getSchema(this.pool, table);
|
|
544
350
|
}
|
|
545
351
|
async getReferencingTables(table) {
|
|
546
|
-
return this.
|
|
547
|
-
const refRes = await this.pool.query(`SELECT
|
|
548
|
-
tc.table_name AS table,
|
|
549
|
-
kcu.column_name AS "from",
|
|
550
|
-
ccu.column_name AS "to"
|
|
551
|
-
FROM information_schema.table_constraints AS tc
|
|
552
|
-
JOIN information_schema.key_column_usage AS kcu
|
|
553
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
554
|
-
AND tc.table_schema = kcu.table_schema
|
|
555
|
-
JOIN information_schema.constraint_column_usage AS ccu
|
|
556
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
557
|
-
AND ccu.table_schema = tc.table_schema
|
|
558
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
559
|
-
AND tc.table_schema = $1
|
|
560
|
-
AND ccu.table_name = $2;`, [this.schema, table]);
|
|
561
|
-
const byTable = new Map();
|
|
562
|
-
for (const r of refRes.rows) {
|
|
563
|
-
if (!byTable.has(r.table))
|
|
564
|
-
byTable.set(r.table, []);
|
|
565
|
-
byTable.get(r.table).push({ from: r.from, to: r.to });
|
|
566
|
-
}
|
|
567
|
-
const results = [];
|
|
568
|
-
for (const [tbl, refs] of byTable.entries()) {
|
|
569
|
-
try {
|
|
570
|
-
const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(tbl)} WHERE ${refs.map((r) => `${(0, generator_1.quoteIdentifier)(r.from)} IS NOT NULL`).join(' OR ')}`);
|
|
571
|
-
results.push({ table: tbl, refs, refCount: Number(countRes.rows[0]?.c ?? 0) });
|
|
572
|
-
}
|
|
573
|
-
catch {
|
|
574
|
-
results.push({ table: tbl, refs, refCount: 0 });
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return results;
|
|
578
|
-
});
|
|
352
|
+
return this.dialectInstance.introspector.getReferencingTables(this.pool, table);
|
|
579
353
|
}
|
|
580
354
|
async renameTable(oldName, newName) {
|
|
581
355
|
if (this.isReadOnly)
|
|
582
356
|
return this.readonlyBlocked();
|
|
583
357
|
return this.tryRun(async () => {
|
|
584
|
-
await this.pool.query(
|
|
358
|
+
await this.pool.query(this.dialectInstance.ddl.renameTable(oldName, newName));
|
|
585
359
|
return { changes: 1 };
|
|
586
360
|
});
|
|
587
361
|
}
|
|
@@ -589,19 +363,13 @@ class PostgresDatabase {
|
|
|
589
363
|
if (this.isReadOnly)
|
|
590
364
|
return this.readonlyBlocked();
|
|
591
365
|
return this.tryRun(async () => {
|
|
592
|
-
|
|
593
|
-
if (col.notNull)
|
|
594
|
-
sql += ' NOT NULL';
|
|
595
|
-
if (col.defaultValue !== undefined && col.defaultValue !== null && col.defaultValue !== '') {
|
|
596
|
-
sql += ` DEFAULT ${col.defaultValue}`;
|
|
597
|
-
}
|
|
598
|
-
if (col.foreignKey) {
|
|
599
|
-
sql += ` REFERENCES ${(0, generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0, generator_1.quoteIdentifier)(col.foreignKey.column)})`;
|
|
600
|
-
}
|
|
601
|
-
await this.pool.query(sql);
|
|
366
|
+
await this.pool.query(this.dialectInstance.ddl.addColumn(table, col));
|
|
602
367
|
if (col.unique) {
|
|
603
|
-
|
|
604
|
-
|
|
368
|
+
await this.pool.query(this.dialectInstance.ddl.createIndex(table, {
|
|
369
|
+
name: `idx_${table}_${col.name}_unique`,
|
|
370
|
+
columns: [col.name],
|
|
371
|
+
unique: true,
|
|
372
|
+
}));
|
|
605
373
|
}
|
|
606
374
|
return { changes: 1 };
|
|
607
375
|
});
|
|
@@ -609,42 +377,13 @@ class PostgresDatabase {
|
|
|
609
377
|
async modifyColumn(table, oldColName, newColDef) {
|
|
610
378
|
if (this.isReadOnly)
|
|
611
379
|
return this.readonlyBlocked();
|
|
612
|
-
return this.
|
|
613
|
-
const newName = String(newColDef.name || oldColName).trim();
|
|
614
|
-
if (oldColName !== newName) {
|
|
615
|
-
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldColName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
616
|
-
}
|
|
617
|
-
const colToModify = newName;
|
|
618
|
-
if (newColDef.type) {
|
|
619
|
-
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} TYPE ${newColDef.type} USING ${(0, generator_1.quoteIdentifier)(colToModify)}::${newColDef.type};`);
|
|
620
|
-
}
|
|
621
|
-
if (newColDef.notNull !== undefined) {
|
|
622
|
-
const action = newColDef.notNull ? 'SET NOT NULL' : 'DROP NOT NULL';
|
|
623
|
-
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
|
|
624
|
-
}
|
|
625
|
-
if (newColDef.defaultValue !== undefined) {
|
|
626
|
-
const action = newColDef.defaultValue === null || newColDef.defaultValue === ''
|
|
627
|
-
? 'DROP DEFAULT'
|
|
628
|
-
: `SET DEFAULT ${newColDef.defaultValue}`;
|
|
629
|
-
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
|
|
630
|
-
}
|
|
631
|
-
if (newColDef.unique !== undefined) {
|
|
632
|
-
const idxName = `idx_${table}_${colToModify}_unique`;
|
|
633
|
-
if (newColDef.unique) {
|
|
634
|
-
await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(colToModify)});`);
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(idxName)};`);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return { changes: 1 };
|
|
641
|
-
});
|
|
380
|
+
return this.dialectInstance.ddl.modifyColumn(this.pool, table, oldColName, newColDef);
|
|
642
381
|
}
|
|
643
382
|
async renameColumn(table, oldName, newName) {
|
|
644
383
|
if (this.isReadOnly)
|
|
645
384
|
return this.readonlyBlocked();
|
|
646
385
|
return this.tryRun(async () => {
|
|
647
|
-
await this.pool.query(
|
|
386
|
+
await this.pool.query(this.dialectInstance.ddl.renameColumn(table, oldName, newName));
|
|
648
387
|
return { changes: 1 };
|
|
649
388
|
});
|
|
650
389
|
}
|
|
@@ -652,7 +391,7 @@ class PostgresDatabase {
|
|
|
652
391
|
if (this.isReadOnly)
|
|
653
392
|
return this.readonlyBlocked();
|
|
654
393
|
return this.tryRun(async () => {
|
|
655
|
-
await this.pool.query(
|
|
394
|
+
await this.pool.query(this.dialectInstance.ddl.dropColumn(table, column));
|
|
656
395
|
return { changes: 1 };
|
|
657
396
|
});
|
|
658
397
|
}
|
|
@@ -660,7 +399,7 @@ class PostgresDatabase {
|
|
|
660
399
|
if (this.isReadOnly)
|
|
661
400
|
return this.readonlyBlocked();
|
|
662
401
|
return this.tryRun(async () => {
|
|
663
|
-
await this.pool.query(
|
|
402
|
+
await this.pool.query(this.dialectInstance.ddl.dropTable(table, { cascade: true }));
|
|
664
403
|
return { changes: 1 };
|
|
665
404
|
});
|
|
666
405
|
}
|
|
@@ -668,10 +407,7 @@ class PostgresDatabase {
|
|
|
668
407
|
if (this.isReadOnly)
|
|
669
408
|
return this.readonlyBlocked();
|
|
670
409
|
return this.tryRun(async () => {
|
|
671
|
-
|
|
672
|
-
const cols = index.columns.map(generator_1.quoteIdentifier).join(', ');
|
|
673
|
-
const name = index.name || `idx_${table}_${index.columns.join('_')}`;
|
|
674
|
-
await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(name)} ON ${(0, generator_1.quoteIdentifier)(table)} (${cols});`);
|
|
410
|
+
await this.pool.query(this.dialectInstance.ddl.createIndex(table, index));
|
|
675
411
|
return { changes: 1 };
|
|
676
412
|
});
|
|
677
413
|
}
|
|
@@ -679,7 +415,7 @@ class PostgresDatabase {
|
|
|
679
415
|
if (this.isReadOnly)
|
|
680
416
|
return this.readonlyBlocked();
|
|
681
417
|
return this.tryRun(async () => {
|
|
682
|
-
await this.pool.query(
|
|
418
|
+
await this.pool.query(this.dialectInstance.ddl.dropIndex(indexName));
|
|
683
419
|
return { changes: 1 };
|
|
684
420
|
});
|
|
685
421
|
}
|
package/dist/db/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import type {FilterValue} from '../utils/common';import type {ColumnDef,IndexDef} from '../sql/generator';export type {FilterCondition,FilterValue} from '../utils/common';export type DatabaseDialect='sqlite'|'postgres';export type SQLInputValue=null|number|bigint|string|boolean|Uint8Array|Buffer|Date;export interface Result<T=unknown>{success:boolean;data?:T;error?:string;}export interface MutationResult{changes?:number;lastInsertRowid?:number|null;}export interface TableListItem{name:string;}export interface ColumnInfo{cid:number;name:string;type:string;notnull:number;dflt_value:string|null;pk:number;}export interface ForeignKeyInfo{id:number;seq:number;table:string; from :string;to:string|null;on_update:string;on_delete:string;}export interface TableInfoData{table:string;columns:ColumnInfo[];foreignKeys:ForeignKeyInfo[];primaryKey:string[];indexes:IndexInfo[];sql:string|null;}export interface
|
|
1
|
+
import type {FilterValue} from '../utils/common';import type {ColumnDef,IndexDef} from '../sql/generator';import type {IDatabaseDriver} from './dialects/types';export type {FilterCondition,FilterValue} from '../utils/common';export type {IDatabaseDriver} from './dialects/types';export type DatabaseDialect='sqlite'|'postgres';export type SQLInputValue=null|number|bigint|string|boolean|Uint8Array|Buffer|Date;export interface Result<T=unknown>{success:boolean;data?:T;error?:string;}export interface MutationResult{changes?:number;lastInsertRowid?:number|null;}export interface TableListItem{name:string;}export interface ColumnInfo{cid:number;name:string;type:string;notnull:number;dflt_value:string|null;pk:number;}export interface ForeignKeyInfo{id:number;seq:number;table:string; from :string;to:string|null;on_update:string;on_delete:string;}export interface TableInfoData{table:string;columns:ColumnInfo[];foreignKeys:ForeignKeyInfo[];primaryKey:string[];indexes:IndexInfo[];sql:string|null;}export interface WhereClause{column:string;value:unknown;}export interface IndexInfo{name:string;unique:boolean;partial:number;origin:string;columns:string[];sql?:string|null;}export interface RowFilters{[column:string]:FilterValue;}export interface ColumnDetail extends ColumnInfo{unique:boolean;indexed:boolean;referencedBy:{table:string; from :string;}[];fk:ForeignKeyInfo|null;canDrop:boolean;dropBlockers:string[];}export interface SchemaInfo{table:string;columns:ColumnDetail[];foreignKeys:ForeignKeyInfo[];indexes:IndexInfo[];references:{table:string; from :string;to:string;}[];}export interface ReferencingTableInfo{table:string;refs:{ from :string;to:string;}[];refCount:number;}export interface DbOpenOptions{readonly ?:boolean;}export interface PostgresOptions extends DbOpenOptions{connectionString?:string;host?:string;port?:number;database?:string;user?:string;password?:string;ssl?:boolean|object;max?:number;schema?:string;}export interface QueryOptions{page?:number;limit?:number;orderBy?:string;orderDir?:'asc'|'desc';filters?:RowFilters;}export interface IDatabase extends IDatabaseDriver{hasMultipleStatements(sql:string):boolean;runWrite(sql:string):Promise<Result<{changes?:number;}>>;listTables():Promise<Result<TableListItem[]>>;getTableInfo(table:string):Promise<Result<TableInfoData>>;getRowCount(table:string,filters?:RowFilters):Promise<Result<number>>;getRows(table:string,opts?:QueryOptions):Promise<Result<Record<string,unknown>[]>>;getAllRows(table:string):Promise<Result<Record<string,unknown>[]>>;getRow(table:string,where:WhereClause[]):Promise<Result<Record<string,unknown>|null>>;insertRow(table:string,fields:WhereClause[]):Promise<Result<MutationResult>>;insertRows(table:string,rows:WhereClause[][]):Promise<Result<{inserted:number;skipped:number;}>>;updateRow(table:string,fields:WhereClause[],where:WhereClause[]):Promise<Result<MutationResult>>;updateRows(table:string,rows:{fields:WhereClause[];where:WhereClause[];}[]):Promise<Result<{updated:number;}>>;deleteRow(table:string,where:WhereClause[]):Promise<Result<MutationResult>>;deleteRows(table:string,rows:WhereClause[][]):Promise<Result<{deleted:number;}>>;getRowsByPks(table:string,pks:WhereClause[][]):Promise<Result<Record<string,unknown>[]>>;getRowsByFk(table:string,column:string,value:unknown,limit?:number):Promise<Result<{rows:Record<string,unknown>[];total:number;}>>;getCreateStatement(table:string):Promise<Result<string|null>>;getSettings():Promise<Result<Record<string,string>>>;getSchema(table:string):Promise<Result<SchemaInfo>>;getReferencingTables(table:string):Promise<Result<ReferencingTableInfo[]>>;renameTable(oldName:string,newName:string):Promise<Result<{changes?:number;}>>;addColumn(table:string,col:ColumnDef):Promise<Result<{changes?:number;}>>;modifyColumn(table:string,oldColName:string,newColDef:ColumnDef):Promise<Result<{changes?:number;}>>;renameColumn(table:string,oldName:string,newName:string):Promise<Result<{changes?:number;}>>;dropColumn(table:string,column:string):Promise<Result<{changes?:number;}>>;dropTable(table:string):Promise<Result<{changes?:number;}>>;createIndex(table:string,index:IndexDef):Promise<Result<{changes?:number;}>>;dropIndex(indexName:string):Promise<Result<{changes?:number;}>>;}
|
package/dist/db/types.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{createRouter,type AppOptions} from './app';export{createLogger,type Logger,type LogLevel} from './utils/logger';export{c,supportsColor} from './utils/colors';export{SqliteDatabase,PostgresDatabase,type IDatabase,type DatabaseDialect,type PostgresOptions,type MutationResult,type QueryOptions,type Result,type TableInfoData,type ColumnInfo,type ForeignKeyInfo,type WhereClause,type IndexInfo,type SchemaInfo,type ColumnDetail,type
|
|
1
|
+
export{createRouter,type AppOptions} from './app';export{createLogger,type Logger,type LogLevel} from './utils/logger';export{c,supportsColor} from './utils/colors';export{SqliteDatabase,PostgresDatabase,getDialect,type IDatabase,type IDialect,type ISchemaIntrospector,type IDdlGenerator,type DialectCapabilities,type DatabaseDialect,type PostgresOptions,type MutationResult,type QueryOptions,type Result,type TableInfoData,type ColumnInfo,type ForeignKeyInfo,type WhereClause,type IndexInfo,type SchemaInfo,type ColumnDetail,type RowFilters,type FilterCondition,type FilterValue,type SQLInputValue} from './db/index';export{DbManager,type DbManagerOptions,type DatabaseEntry} from './db/manager';export{type ColumnDef,type IndexDef} from './sql/generator';export{type ColumnPlan,type ColumnGeneratorConfig,type GeneratorStrategyId,type StrategyDescriptor,type GenerateResult} from './data/index';export{type AuthConfig,type ResolvedAuthConfig,resolveAuthConfig,getOrCreatePersistentSecret,createSessionToken,verifySessionToken,parseCookies,constantTimeCompare,hashPassword,verifyPassword,DEFAULT_USERNAME,DEFAULT_PASSWORD_HASH,SESSION_COOKIE_NAME} from './auth/index';export{isServerlessEnvironment,createServerlessHandler,createLambdaHandler,type LambdaProxyResult} from './serverless';export{sniffMimeType,generateHexDump,analyzeBlob,isJsonString,formatJsonSafely,type MimeAnalysis,type HexDumpLine,type HexDumpResult,type BlobMetadata} from './utils/datatype';export * from './core/index';export * from './modules/index';export * from './types/api';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.formatJsonSafely = exports.isJsonString = exports.analyzeBlob = exports.generateHexDump = exports.sniffMimeType = exports.createLambdaHandler = exports.createServerlessHandler = exports.isServerlessEnvironment = exports.SESSION_COOKIE_NAME = exports.DEFAULT_PASSWORD_HASH = exports.DEFAULT_USERNAME = exports.verifyPassword = exports.hashPassword = exports.constantTimeCompare = exports.parseCookies = exports.verifySessionToken = exports.createSessionToken = exports.getOrCreatePersistentSecret = exports.resolveAuthConfig = exports.DbManager = exports.PostgresDatabase = exports.SqliteDatabase = exports.supportsColor = exports.c = exports.createLogger = exports.createRouter = void 0;
|
|
17
|
+
exports.formatJsonSafely = exports.isJsonString = exports.analyzeBlob = exports.generateHexDump = exports.sniffMimeType = exports.createLambdaHandler = exports.createServerlessHandler = exports.isServerlessEnvironment = exports.SESSION_COOKIE_NAME = exports.DEFAULT_PASSWORD_HASH = exports.DEFAULT_USERNAME = exports.verifyPassword = exports.hashPassword = exports.constantTimeCompare = exports.parseCookies = exports.verifySessionToken = exports.createSessionToken = exports.getOrCreatePersistentSecret = exports.resolveAuthConfig = exports.DbManager = exports.getDialect = exports.PostgresDatabase = exports.SqliteDatabase = exports.supportsColor = exports.c = exports.createLogger = exports.createRouter = void 0;
|
|
18
18
|
var app_1 = require("./app");
|
|
19
19
|
Object.defineProperty(exports, "createRouter", { enumerable: true, get: function () { return app_1.createRouter; } });
|
|
20
20
|
var logger_1 = require("./utils/logger");
|
|
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "supportsColor", { enumerable: true, get: functio
|
|
|
25
25
|
var index_1 = require("./db/index");
|
|
26
26
|
Object.defineProperty(exports, "SqliteDatabase", { enumerable: true, get: function () { return index_1.SqliteDatabase; } });
|
|
27
27
|
Object.defineProperty(exports, "PostgresDatabase", { enumerable: true, get: function () { return index_1.PostgresDatabase; } });
|
|
28
|
+
Object.defineProperty(exports, "getDialect", { enumerable: true, get: function () { return index_1.getDialect; } });
|
|
28
29
|
var manager_1 = require("./db/manager");
|
|
29
30
|
Object.defineProperty(exports, "DbManager", { enumerable: true, get: function () { return manager_1.DbManager; } });
|
|
30
31
|
var index_2 = require("./auth/index");
|
|
@@ -49,4 +50,6 @@ Object.defineProperty(exports, "generateHexDump", { enumerable: true, get: funct
|
|
|
49
50
|
Object.defineProperty(exports, "analyzeBlob", { enumerable: true, get: function () { return datatype_1.analyzeBlob; } });
|
|
50
51
|
Object.defineProperty(exports, "isJsonString", { enumerable: true, get: function () { return datatype_1.isJsonString; } });
|
|
51
52
|
Object.defineProperty(exports, "formatJsonSafely", { enumerable: true, get: function () { return datatype_1.formatJsonSafely; } });
|
|
53
|
+
__exportStar(require("./core/index"), exports);
|
|
54
|
+
__exportStar(require("./modules/index"), exports);
|
|
52
55
|
__exportStar(require("./types/api"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import type {DbManager} from '../../db/manager';import type {Logger} from '../../utils/logger';import{type DatabaseRow,type FsEntry} from './databases.service';export type {DatabaseRow,FsEntry};export interface DatabasesRoutesContext{manager:DbManager;logger:Logger;basePath:string;readonly ?:boolean;allowBrowse?:boolean;browseRoot?:string;invalidate?:(id:string)=>void;}export declare function registerDatabasesRoutes(router:Router,ctx:DatabasesRoutesContext):void;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerDatabasesRoutes = registerDatabasesRoutes;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const common_1 = require("../../utils/common");
|
|
9
|
+
const databases_service_1 = require("./databases.service");
|
|
10
|
+
function registerDatabasesRoutes(router, ctx) {
|
|
11
|
+
const { manager } = ctx;
|
|
12
|
+
const allowBrowse = ctx.allowBrowse ?? true;
|
|
13
|
+
const browseRoot = ctx.browseRoot ? node_path_1.default.resolve(ctx.browseRoot) : undefined;
|
|
14
|
+
const wrap = (fn) => async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
await fn(req, res);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
res.status(500).json({ success: false, error: (0, common_1.errorMessage)(err) });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
router.get('/', async (_req, res, next) => {
|
|
23
|
+
try {
|
|
24
|
+
res.locals.databasesMode = true;
|
|
25
|
+
res.render('pages/databases', {
|
|
26
|
+
title: 'Databases',
|
|
27
|
+
databasesMode: true,
|
|
28
|
+
databases: await databases_service_1.DatabasesService.getDatabaseRows(manager),
|
|
29
|
+
dbDir: manager.directory,
|
|
30
|
+
readonly: Boolean(ctx.readonly),
|
|
31
|
+
allowBrowse,
|
|
32
|
+
browseRoot,
|
|
33
|
+
config: { basePath: ctx.basePath },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
next(err);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
router.get('/api/databases', wrap(async (_req, res) => {
|
|
41
|
+
res.json({ success: true, data: await databases_service_1.DatabasesService.getDatabaseRows(manager) });
|
|
42
|
+
}));
|
|
43
|
+
router.get('/api/fs/list', wrap(async (req, res) => {
|
|
44
|
+
if (!allowBrowse) {
|
|
45
|
+
return res.status(403).json({
|
|
46
|
+
success: false,
|
|
47
|
+
error: 'File browsing is disabled — this server was started with specific database files.',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const raw = String(req.query.path ?? '').trim();
|
|
51
|
+
const base = raw ? node_path_1.default.resolve(raw) : manager.directory;
|
|
52
|
+
try {
|
|
53
|
+
const data = databases_service_1.DatabasesService.listDirectory(base, manager, browseRoot);
|
|
54
|
+
res.json({ success: true, data });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
58
|
+
if (msg.includes('outside the allowed folder'))
|
|
59
|
+
return res.status(403).json({ success: false, error: msg });
|
|
60
|
+
if (msg.includes('does not exist'))
|
|
61
|
+
return res.status(404).json({ success: false, error: msg });
|
|
62
|
+
return res.status(400).json({ success: false, error: msg });
|
|
63
|
+
}
|
|
64
|
+
}));
|
|
65
|
+
router.post('/api/databases/open', wrap(async (req, res) => {
|
|
66
|
+
if (!allowBrowse) {
|
|
67
|
+
return res.status(403).json({
|
|
68
|
+
success: false,
|
|
69
|
+
error: 'File browsing is disabled — this server was started with specific database files.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const p = String(req.body?.path ?? '').trim();
|
|
73
|
+
const isRo = req.body?.readonly !== undefined ? Boolean(req.body.readonly) : undefined;
|
|
74
|
+
try {
|
|
75
|
+
const id = databases_service_1.DatabasesService.openDatabaseFile(manager, p, isRo, browseRoot);
|
|
76
|
+
res.json({ success: true, data: { id, message: `Opened "${id}".` } });
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
80
|
+
if (msg.includes('outside the allowed folder'))
|
|
81
|
+
return res.status(403).json({ success: false, error: msg });
|
|
82
|
+
return res.status(400).json({ success: false, error: msg });
|
|
83
|
+
}
|
|
84
|
+
}));
|
|
85
|
+
router.post('/api/databases/connect-postgres', wrap(async (req, res) => {
|
|
86
|
+
if (ctx.readonly)
|
|
87
|
+
return res.status(403).json({ success: false, error: 'Read-only mode — adding connections is disabled.' });
|
|
88
|
+
const name = String(req.body?.name ?? '').trim();
|
|
89
|
+
const connectionString = String(req.body?.connectionString ?? req.body?.connection ?? '').trim();
|
|
90
|
+
const isRo = Boolean(req.body?.readonly);
|
|
91
|
+
try {
|
|
92
|
+
const id = await databases_service_1.DatabasesService.connectPostgres(manager, name, connectionString, isRo);
|
|
93
|
+
res.status(201).json({ success: true, data: { id, message: `Connected to PostgreSQL database "${id}".` } });
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
res.status(400).json({ success: false, error: (0, common_1.errorMessage)(err) });
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
router.post('/api/databases/:id/mode', wrap(async (req, res) => {
|
|
100
|
+
if (ctx.readonly && req.body?.readonly === false) {
|
|
101
|
+
return res.status(403).json({ success: false, error: 'Server is in global read-only mode.' });
|
|
102
|
+
}
|
|
103
|
+
const id = String(req.params.id);
|
|
104
|
+
const targetRo = Boolean(req.body?.readonly);
|
|
105
|
+
try {
|
|
106
|
+
databases_service_1.DatabasesService.setDatabaseMode(manager, id, targetRo, ctx.invalidate);
|
|
107
|
+
res.json({
|
|
108
|
+
success: true,
|
|
109
|
+
data: {
|
|
110
|
+
id,
|
|
111
|
+
readonly: targetRo,
|
|
112
|
+
message: `Database "${id}" mode set to ${targetRo ? 'Read-only' : 'Writable'}.`,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
118
|
+
if (msg.includes('does not exist'))
|
|
119
|
+
return res.status(404).json({ success: false, error: msg });
|
|
120
|
+
res.status(400).json({ success: false, error: msg });
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
123
|
+
router.post('/api/databases', wrap(async (req, res) => {
|
|
124
|
+
if (ctx.readonly)
|
|
125
|
+
return res.status(403).json({ success: false, error: 'Read-only mode — creating databases is disabled.' });
|
|
126
|
+
const name = String(req.body?.name ?? '').trim();
|
|
127
|
+
try {
|
|
128
|
+
const id = databases_service_1.DatabasesService.createDatabase(manager, name);
|
|
129
|
+
res.status(201).json({ success: true, data: { id, message: `Database "${id}" created.` } });
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
res.status(400).json({ success: false, error: (0, common_1.errorMessage)(err) });
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
router.delete('/api/databases/:id', wrap(async (req, res) => {
|
|
136
|
+
if (ctx.readonly)
|
|
137
|
+
return res.status(403).json({ success: false, error: 'Read-only mode — deleting databases is disabled.' });
|
|
138
|
+
const id = String(req.params.id);
|
|
139
|
+
try {
|
|
140
|
+
databases_service_1.DatabasesService.removeDatabase(manager, id, ctx.invalidate);
|
|
141
|
+
res.json({ success: true, data: { message: `Database "${id}" removed.` } });
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
145
|
+
if (msg.includes('does not exist'))
|
|
146
|
+
return res.status(404).json({ success: false, error: msg });
|
|
147
|
+
res.status(400).json({ success: false, error: msg });
|
|
148
|
+
}
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {DbManager} from '../../db/manager';import type {DatabaseRow,FsEntry} from '../../types/api';export type {DatabaseRow,FsEntry};export declare class DatabasesService{static getDatabaseRows(manager:DbManager):Promise<DatabaseRow[]>;static listDirectory(base:string,manager:DbManager,browseRoot?:string):{path:string;name:string;parent:string|null;entries:FsEntry[];};static openDatabaseFile(manager:DbManager,filePath:string,isRo?:boolean,browseRoot?:string):string;static connectPostgres(manager:DbManager,name:string,connectionString:string,isRo?:boolean):Promise<string>;static createDatabase(manager:DbManager,name:string):string;static setDatabaseMode(manager:DbManager,id:string,readonly :boolean,invalidate?:(id:string)=>void):void;static removeDatabase(manager:DbManager,id:string,invalidate?:(id:string)=>void):void;}
|