admindb 2.4.1 → 2.5.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 +36 -25
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.js +2 -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/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 +214 -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 +62 -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/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/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/js/browse.js +1 -1
- package/dist/public/js/query.js +1 -1
- 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/stream.d.ts +1 -0
- package/dist/utils/stream.js +131 -0
- 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/table.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 +4 -2
- 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
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerTableRoutes = registerTableRoutes;
|
|
4
|
-
const generator_1 = require("../../sql/generator");
|
|
5
|
-
const helpers_1 = require("./helpers");
|
|
6
|
-
function registerTableRoutes(router, ctx) {
|
|
7
|
-
router.get('/api/tables/:db', (0, helpers_1.wrap)(async (req, res) => {
|
|
8
|
-
const db = ctx.getDb(req);
|
|
9
|
-
const r = await db.listTables();
|
|
10
|
-
if (!r.success)
|
|
11
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to list tables.', 500);
|
|
12
|
-
(0, helpers_1.ok)(res, r.data);
|
|
13
|
-
}));
|
|
14
|
-
router.get('/api/tables/:db/:table/info', (0, helpers_1.wrap)(async (req, res) => {
|
|
15
|
-
const db = ctx.getDb(req);
|
|
16
|
-
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
17
|
-
if (!info)
|
|
18
|
-
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
19
|
-
(0, helpers_1.ok)(res, info);
|
|
20
|
-
}));
|
|
21
|
-
router.get('/api/tables/:db/:table/fk-options', (0, helpers_1.wrap)(async (req, res) => {
|
|
22
|
-
const db = ctx.getDb(req);
|
|
23
|
-
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
24
|
-
if (!info)
|
|
25
|
-
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
26
|
-
const options = {};
|
|
27
|
-
for (const fk of info.foreignKeys) {
|
|
28
|
-
try {
|
|
29
|
-
const refInfo = await (0, helpers_1.requireTable)(db, fk.table);
|
|
30
|
-
if (!refInfo)
|
|
31
|
-
continue;
|
|
32
|
-
const refCol = fk.to || refInfo.primaryKey[0] || refInfo.columns[0]?.name;
|
|
33
|
-
const pk = refInfo.primaryKey[0] ?? refInfo.columns[0]?.name;
|
|
34
|
-
const labelCol = refInfo.columns.find((c) => /name|title|label|username|email/i.test(c.name))?.name ?? pk;
|
|
35
|
-
const rows = await db.getRows(fk.table, { limit: 100, orderBy: labelCol });
|
|
36
|
-
if (rows.success && rows.data) {
|
|
37
|
-
options[fk.from] = rows.data.map((r) => ({
|
|
38
|
-
value: r[refCol],
|
|
39
|
-
label: r[labelCol] != null ? `${r[labelCol]} (${r[refCol]})` : String(r[refCol]),
|
|
40
|
-
}));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch { }
|
|
44
|
-
}
|
|
45
|
-
(0, helpers_1.ok)(res, options);
|
|
46
|
-
}));
|
|
47
|
-
router.get('/api/tables/:db/:table/ddl', (0, helpers_1.wrap)(async (req, res) => {
|
|
48
|
-
const db = ctx.getDb(req);
|
|
49
|
-
const r = await db.getCreateStatement(req.params.table);
|
|
50
|
-
if (!r.success)
|
|
51
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to fetch DDL.', 500);
|
|
52
|
-
if (!r.data)
|
|
53
|
-
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
54
|
-
(0, helpers_1.ok)(res, { sql: r.data });
|
|
55
|
-
}));
|
|
56
|
-
router.post('/api/tables/:db/:table/rename', (0, helpers_1.wrap)(async (req, res) => {
|
|
57
|
-
const db = ctx.getDb(req);
|
|
58
|
-
const newName = String(req.body?.name ?? '').trim();
|
|
59
|
-
if (!newName)
|
|
60
|
-
return (0, helpers_1.fail)(res, 'New table name is required.');
|
|
61
|
-
const r = await db.renameTable(req.params.table, newName);
|
|
62
|
-
if (!r.success)
|
|
63
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to rename table.');
|
|
64
|
-
(0, helpers_1.ok)(res, { message: `Table renamed to "${newName}".`, name: newName });
|
|
65
|
-
}));
|
|
66
|
-
router.delete('/api/tables/:db/:table', (0, helpers_1.wrap)(async (req, res) => {
|
|
67
|
-
const db = ctx.getDb(req);
|
|
68
|
-
const r = await db.dropTable(req.params.table);
|
|
69
|
-
if (!r.success)
|
|
70
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop table.');
|
|
71
|
-
(0, helpers_1.ok)(res, { message: `Table "${req.params.table}" dropped.` });
|
|
72
|
-
}));
|
|
73
|
-
router.post('/api/tables/:db/bulk-drop', (0, helpers_1.wrap)(async (req, res) => {
|
|
74
|
-
const db = ctx.getDb(req);
|
|
75
|
-
const tables = Array.isArray(req.body?.tables)
|
|
76
|
-
? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
|
77
|
-
: [];
|
|
78
|
-
const force = Boolean(req.body?.force);
|
|
79
|
-
if (tables.length === 0)
|
|
80
|
-
return (0, helpers_1.fail)(res, 'No tables specified.');
|
|
81
|
-
const dropped = [];
|
|
82
|
-
const failed = [];
|
|
83
|
-
if (force && db.dialect === 'sqlite')
|
|
84
|
-
await db.run('PRAGMA foreign_keys = OFF');
|
|
85
|
-
for (const table of tables) {
|
|
86
|
-
try {
|
|
87
|
-
const r = force && db.dialect === 'postgres'
|
|
88
|
-
? await db.run(`DROP TABLE IF EXISTS ${(0, generator_1.quoteIdentifier)(table)} CASCADE`)
|
|
89
|
-
: await db.dropTable(table);
|
|
90
|
-
if (!r.success)
|
|
91
|
-
throw new Error(r.error ?? 'Failed to drop table.');
|
|
92
|
-
dropped.push(table);
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
failed.push({ table, error: err.message });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (force && db.dialect === 'sqlite')
|
|
99
|
-
await db.run('PRAGMA foreign_keys = ON');
|
|
100
|
-
(0, helpers_1.ok)(res, {
|
|
101
|
-
dropped,
|
|
102
|
-
failed,
|
|
103
|
-
message: `Dropped ${dropped.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
|
|
104
|
-
});
|
|
105
|
-
}));
|
|
106
|
-
router.post('/api/tables/:db/bulk-truncate', (0, helpers_1.wrap)(async (req, res) => {
|
|
107
|
-
const db = ctx.getDb(req);
|
|
108
|
-
const tables = Array.isArray(req.body?.tables)
|
|
109
|
-
? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
|
110
|
-
: [];
|
|
111
|
-
const force = Boolean(req.body?.force);
|
|
112
|
-
if (tables.length === 0)
|
|
113
|
-
return (0, helpers_1.fail)(res, 'No tables specified.');
|
|
114
|
-
const cleared = [];
|
|
115
|
-
const failed = [];
|
|
116
|
-
if (force && db.dialect === 'sqlite')
|
|
117
|
-
await db.run('PRAGMA foreign_keys = OFF');
|
|
118
|
-
for (const table of tables) {
|
|
119
|
-
try {
|
|
120
|
-
const r = force && db.dialect === 'postgres'
|
|
121
|
-
? await db.run(`TRUNCATE ${(0, generator_1.quoteIdentifier)(table)} RESTART IDENTITY CASCADE`)
|
|
122
|
-
: await db.run(`DELETE FROM ${(0, generator_1.quoteIdentifier)(table)}`);
|
|
123
|
-
if (!r.success)
|
|
124
|
-
throw new Error(r.error ?? 'Failed to clear table.');
|
|
125
|
-
cleared.push(table);
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
failed.push({ table, error: err.message });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (force && db.dialect === 'sqlite')
|
|
132
|
-
await db.run('PRAGMA foreign_keys = ON');
|
|
133
|
-
(0, helpers_1.ok)(res, {
|
|
134
|
-
cleared,
|
|
135
|
-
failed,
|
|
136
|
-
message: `Cleared ${cleared.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
|
|
137
|
-
});
|
|
138
|
-
}));
|
|
139
|
-
router.post('/api/tables/:db/generate', (0, helpers_1.wrap)(async (req, res) => {
|
|
140
|
-
const name = String(req.body?.name ?? '').trim();
|
|
141
|
-
const columns = (req.body?.columns ?? []);
|
|
142
|
-
if (!name)
|
|
143
|
-
return (0, helpers_1.fail)(res, 'Table name is required.');
|
|
144
|
-
if (!Array.isArray(columns) || columns.length === 0)
|
|
145
|
-
return (0, helpers_1.fail)(res, 'At least one column is required.');
|
|
146
|
-
try {
|
|
147
|
-
(0, helpers_1.ok)(res, { sql: (0, generator_1.generateCreateTable)(name, columns) });
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
(0, helpers_1.fail)(res, err.message);
|
|
151
|
-
}
|
|
152
|
-
}));
|
|
153
|
-
const createTableHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
154
|
-
const db = ctx.getDb(req);
|
|
155
|
-
const name = String(req.body?.name ?? '').trim();
|
|
156
|
-
const columns = (req.body?.columns ?? []);
|
|
157
|
-
if (!name)
|
|
158
|
-
return (0, helpers_1.fail)(res, 'Table name is required.');
|
|
159
|
-
if (!Array.isArray(columns) || columns.length === 0)
|
|
160
|
-
return (0, helpers_1.fail)(res, 'At least one column is required.');
|
|
161
|
-
let sql;
|
|
162
|
-
try {
|
|
163
|
-
sql = (0, generator_1.generateCreateTable)(name, columns);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
return (0, helpers_1.fail)(res, err.message);
|
|
167
|
-
}
|
|
168
|
-
const r = await db.execResult(sql);
|
|
169
|
-
if (!r.success)
|
|
170
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to create table.');
|
|
171
|
-
(0, helpers_1.ok)(res, { message: `Table "${name}" created.`, name }, 201);
|
|
172
|
-
});
|
|
173
|
-
router.post('/api/tables/:db', createTableHandler);
|
|
174
|
-
router.post('/api/tables/:db/create', createTableHandler);
|
|
175
|
-
router.post('/api/tables/:db/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
|
|
176
|
-
const db = ctx.getDb(req);
|
|
177
|
-
const col = req.body;
|
|
178
|
-
if (!col?.name)
|
|
179
|
-
return (0, helpers_1.fail)(res, 'Column name is required.');
|
|
180
|
-
const r = await db.addColumn(req.params.table, col);
|
|
181
|
-
if (!r.success)
|
|
182
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to add column.');
|
|
183
|
-
(0, helpers_1.ok)(res, { message: `Column "${col.name}" added.` }, 201);
|
|
184
|
-
}));
|
|
185
|
-
router.put('/api/tables/:db/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
186
|
-
const db = ctx.getDb(req);
|
|
187
|
-
const col = req.body;
|
|
188
|
-
if (!col)
|
|
189
|
-
return (0, helpers_1.fail)(res, 'Column definition is required.');
|
|
190
|
-
const r = await db.modifyColumn(req.params.table, req.params.column, col);
|
|
191
|
-
if (!r.success)
|
|
192
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to modify column.');
|
|
193
|
-
(0, helpers_1.ok)(res, { message: `Column "${req.params.column}" updated.` });
|
|
194
|
-
}));
|
|
195
|
-
router.post('/api/tables/:db/:table/columns/:column/rename', (0, helpers_1.wrap)(async (req, res) => {
|
|
196
|
-
const db = ctx.getDb(req);
|
|
197
|
-
const newName = String(req.body?.name ?? '').trim();
|
|
198
|
-
if (!newName)
|
|
199
|
-
return (0, helpers_1.fail)(res, 'New column name is required.');
|
|
200
|
-
const r = await db.renameColumn(req.params.table, req.params.column, newName);
|
|
201
|
-
if (!r.success)
|
|
202
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to rename column.');
|
|
203
|
-
(0, helpers_1.ok)(res, { message: `Column renamed to "${newName}".`, name: newName });
|
|
204
|
-
}));
|
|
205
|
-
router.delete('/api/tables/:db/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
|
|
206
|
-
const db = ctx.getDb(req);
|
|
207
|
-
const r = await db.dropColumn(req.params.table, req.params.column);
|
|
208
|
-
if (!r.success)
|
|
209
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop column.');
|
|
210
|
-
(0, helpers_1.ok)(res, { message: `Column "${req.params.column}" dropped.` });
|
|
211
|
-
}));
|
|
212
|
-
router.post('/api/tables/:db/:table/indexes', (0, helpers_1.wrap)(async (req, res) => {
|
|
213
|
-
const db = ctx.getDb(req);
|
|
214
|
-
const def = req.body;
|
|
215
|
-
if (!def || !Array.isArray(def.columns) || def.columns.length === 0) {
|
|
216
|
-
return (0, helpers_1.fail)(res, 'At least one column is required for an index.');
|
|
217
|
-
}
|
|
218
|
-
const r = await db.createIndex(req.params.table, def);
|
|
219
|
-
if (!r.success)
|
|
220
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to create index.');
|
|
221
|
-
(0, helpers_1.ok)(res, { message: 'Index created.' }, 201);
|
|
222
|
-
}));
|
|
223
|
-
router.delete('/api/tables/:db/:table/indexes/:index', (0, helpers_1.wrap)(async (req, res) => {
|
|
224
|
-
const db = ctx.getDb(req);
|
|
225
|
-
const r = await db.dropIndex(req.params.index);
|
|
226
|
-
if (!r.success)
|
|
227
|
-
return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop index.');
|
|
228
|
-
(0, helpers_1.ok)(res, { message: `Index "${req.params.index}" dropped.` });
|
|
229
|
-
}));
|
|
230
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import type {Router} from 'express';import type {DbManager,DatabaseEntry} from '../db/manager';import type {Logger} from '../utils/logger';interface DatabasesContext{manager:DbManager;logger:Logger;basePath:string;readonly ?:boolean;allowBrowse?:boolean;browseRoot?:string;invalidate?:(id:string)=>void;}export interface DatabaseRow extends DatabaseEntry{sizeLabel:string;modifiedLabel:string;tables:number;}export interface FsEntry{name:string;path:string;isDir:boolean;isDb:boolean;size:number;}export declare function registerDatabasesRoutes(router:Router,ctx:DatabasesContext):void;export{};
|
package/dist/routes/databases.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
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_fs_1 = require("node:fs");
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const common_1 = require("../utils/common");
|
|
10
|
-
function formatDate(iso) {
|
|
11
|
-
try {
|
|
12
|
-
return new Date(iso).toLocaleString();
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return iso;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
async function getDatabaseRows(manager) {
|
|
19
|
-
const entries = manager.list();
|
|
20
|
-
return Promise.all(entries.map(async (e) => ({
|
|
21
|
-
...e,
|
|
22
|
-
isPostgres: e.dialect === 'postgres',
|
|
23
|
-
isSqlite: e.dialect === 'sqlite',
|
|
24
|
-
sizeLabel: e.dialect === 'postgres' ? 'Remote' : (0, common_1.formatBytes)(e.size),
|
|
25
|
-
modifiedLabel: e.dialect === 'postgres' ? 'Connected' : (e.modified ? formatDate(e.modified) : '—'),
|
|
26
|
-
tables: await manager.countTables(e.id),
|
|
27
|
-
})));
|
|
28
|
-
}
|
|
29
|
-
function registerDatabasesRoutes(router, ctx) {
|
|
30
|
-
const { manager } = ctx;
|
|
31
|
-
const allowBrowse = ctx.allowBrowse ?? true;
|
|
32
|
-
const browseRoot = ctx.browseRoot ? node_path_1.default.resolve(ctx.browseRoot) : undefined;
|
|
33
|
-
const wrap = (fn) => async (req, res) => {
|
|
34
|
-
try {
|
|
35
|
-
await fn(req, res);
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
res.status(500).json({ success: false, error: (0, common_1.errorMessage)(err) });
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
router.get('/', async (_req, res, next) => {
|
|
42
|
-
try {
|
|
43
|
-
res.locals.databasesMode = true;
|
|
44
|
-
res.render('pages/databases', {
|
|
45
|
-
title: 'Databases',
|
|
46
|
-
databasesMode: true,
|
|
47
|
-
databases: await getDatabaseRows(manager),
|
|
48
|
-
dbDir: manager.directory,
|
|
49
|
-
readonly: Boolean(ctx.readonly),
|
|
50
|
-
allowBrowse,
|
|
51
|
-
browseRoot,
|
|
52
|
-
config: { basePath: ctx.basePath },
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
next(err);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
router.get('/api/databases', wrap(async (_req, res) => {
|
|
60
|
-
res.json({ success: true, data: await getDatabaseRows(manager) });
|
|
61
|
-
}));
|
|
62
|
-
const BLOCKED_NAMES = new Set([
|
|
63
|
-
'node_modules',
|
|
64
|
-
'.git',
|
|
65
|
-
'.svn',
|
|
66
|
-
'.hg',
|
|
67
|
-
'System Volume Information',
|
|
68
|
-
'$RECYCLE.BIN',
|
|
69
|
-
'.env',
|
|
70
|
-
'.aws',
|
|
71
|
-
'.ssh',
|
|
72
|
-
]);
|
|
73
|
-
function listDirectory(abs) {
|
|
74
|
-
const entries = [];
|
|
75
|
-
for (const name of (0, node_fs_1.readdirSync)(abs)) {
|
|
76
|
-
if (name.startsWith('.') || BLOCKED_NAMES.has(name) || name.toLowerCase().startsWith('.env'))
|
|
77
|
-
continue;
|
|
78
|
-
const full = node_path_1.default.join(abs, name);
|
|
79
|
-
let isDir = false;
|
|
80
|
-
let size = 0;
|
|
81
|
-
try {
|
|
82
|
-
const st = (0, node_fs_1.statSync)(full);
|
|
83
|
-
isDir = st.isDirectory();
|
|
84
|
-
size = isDir ? 0 : st.size;
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (!isDir && !manager.isDbFile(name))
|
|
90
|
-
continue;
|
|
91
|
-
if (browseRoot && !(0, common_1.isPathWithinRoot)(browseRoot, full))
|
|
92
|
-
continue;
|
|
93
|
-
entries.push({ name, path: full, isDir, isDb: !isDir, size });
|
|
94
|
-
}
|
|
95
|
-
entries.sort((a, b) => a.isDir === b.isDir ? a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) : a.isDir ? -1 : 1);
|
|
96
|
-
return entries;
|
|
97
|
-
}
|
|
98
|
-
router.get('/api/fs/list', wrap(async (req, res) => {
|
|
99
|
-
if (!allowBrowse) {
|
|
100
|
-
return res.status(403).json({ success: false, error: 'File browsing is disabled — this server was started with specific database files.' });
|
|
101
|
-
}
|
|
102
|
-
const raw = String(req.query.path ?? '').trim();
|
|
103
|
-
if (raw.includes('\0'))
|
|
104
|
-
return res.status(400).json({ success: false, error: 'Invalid path.' });
|
|
105
|
-
const base = raw ? node_path_1.default.resolve(raw) : manager.directory;
|
|
106
|
-
if (browseRoot && !(0, common_1.isPathWithinRoot)(browseRoot, base)) {
|
|
107
|
-
return res.status(403).json({ success: false, error: `Path is outside the allowed folder: ${browseRoot}` });
|
|
108
|
-
}
|
|
109
|
-
if (!(0, node_fs_1.existsSync)(base))
|
|
110
|
-
return res.status(404).json({ success: false, error: `Path does not exist: ${base}` });
|
|
111
|
-
if (!(0, node_fs_1.statSync)(base).isDirectory())
|
|
112
|
-
return res.status(400).json({ success: false, error: `Not a directory: ${base}` });
|
|
113
|
-
let entries;
|
|
114
|
-
try {
|
|
115
|
-
entries = listDirectory(base);
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
return res.status(400).json({ success: false, error: `Could not list "${base}": ${(0, common_1.errorMessage)(err)}` });
|
|
119
|
-
}
|
|
120
|
-
const parent = node_path_1.default.dirname(base) === base ? null : node_path_1.default.dirname(base);
|
|
121
|
-
const effectiveParent = parent && (!browseRoot || (0, common_1.isPathWithinRoot)(browseRoot, parent)) ? parent : null;
|
|
122
|
-
res.json({
|
|
123
|
-
success: true,
|
|
124
|
-
data: {
|
|
125
|
-
path: base,
|
|
126
|
-
name: node_path_1.default.basename(base) || base,
|
|
127
|
-
parent: effectiveParent,
|
|
128
|
-
entries,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
}));
|
|
132
|
-
router.post('/api/databases/open', wrap(async (req, res) => {
|
|
133
|
-
if (!allowBrowse) {
|
|
134
|
-
return res.status(403).json({ success: false, error: 'File browsing is disabled — this server was started with specific database files.' });
|
|
135
|
-
}
|
|
136
|
-
const p = String(req.body?.path ?? '').trim();
|
|
137
|
-
if (!p || p.includes('\0'))
|
|
138
|
-
return res.status(400).json({ success: false, error: 'Valid database path is required.' });
|
|
139
|
-
const abs = node_path_1.default.resolve(p);
|
|
140
|
-
if (browseRoot && !(0, common_1.isPathWithinRoot)(browseRoot, abs)) {
|
|
141
|
-
return res.status(403).json({ success: false, error: `Cannot open a database outside the allowed folder: ${browseRoot}` });
|
|
142
|
-
}
|
|
143
|
-
const isRo = req.body?.readonly !== undefined ? Boolean(req.body.readonly) : undefined;
|
|
144
|
-
const id = manager.openFile(p, isRo);
|
|
145
|
-
res.json({ success: true, data: { id, message: `Opened "${id}".` } });
|
|
146
|
-
}));
|
|
147
|
-
router.post('/api/databases/connect-postgres', wrap(async (req, res) => {
|
|
148
|
-
if (ctx.readonly)
|
|
149
|
-
return res.status(403).json({ success: false, error: 'Read-only mode — adding connections is disabled.' });
|
|
150
|
-
const name = String(req.body?.name ?? '').trim();
|
|
151
|
-
const connectionString = String(req.body?.connectionString ?? req.body?.connection ?? '').trim();
|
|
152
|
-
const isRo = Boolean(req.body?.readonly);
|
|
153
|
-
if (!connectionString) {
|
|
154
|
-
return res.status(400).json({ success: false, error: 'PostgreSQL connection string is required (e.g. postgresql://user:password@localhost:5432/dbname).' });
|
|
155
|
-
}
|
|
156
|
-
if (!(0, common_1.isPostgresConnectionString)(connectionString)) {
|
|
157
|
-
return res.status(400).json({ success: false, error: 'Invalid connection protocol. Connection string must start with postgres:// or postgresql://' });
|
|
158
|
-
}
|
|
159
|
-
let id;
|
|
160
|
-
try {
|
|
161
|
-
id = manager.addConnection(name, connectionString, isRo);
|
|
162
|
-
const db = manager.open(id);
|
|
163
|
-
await db.listTables();
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
if (id)
|
|
167
|
-
manager.remove(id);
|
|
168
|
-
return res.status(400).json({ success: false, error: `Failed to connect to PostgreSQL: ${(0, common_1.errorMessage)(err)}` });
|
|
169
|
-
}
|
|
170
|
-
res.status(201).json({ success: true, data: { id, message: `Connected to PostgreSQL database "${id}".` } });
|
|
171
|
-
}));
|
|
172
|
-
router.post('/api/databases/:id/mode', wrap(async (req, res) => {
|
|
173
|
-
if (ctx.readonly && req.body?.readonly === false) {
|
|
174
|
-
return res.status(403).json({ success: false, error: 'Server is in global read-only mode.' });
|
|
175
|
-
}
|
|
176
|
-
const id = String(req.params.id);
|
|
177
|
-
if (!manager.has(id))
|
|
178
|
-
return res.status(404).json({ success: false, error: `Database "${id}" does not exist.` });
|
|
179
|
-
const targetRo = Boolean(req.body?.readonly);
|
|
180
|
-
manager.setReadonly(id, targetRo);
|
|
181
|
-
if (ctx.invalidate)
|
|
182
|
-
ctx.invalidate(id);
|
|
183
|
-
res.json({
|
|
184
|
-
success: true,
|
|
185
|
-
data: {
|
|
186
|
-
id,
|
|
187
|
-
readonly: targetRo,
|
|
188
|
-
message: `Database "${id}" mode set to ${targetRo ? 'Read-only' : 'Writable'}.`,
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
}));
|
|
192
|
-
router.post('/api/databases', wrap(async (req, res) => {
|
|
193
|
-
if (ctx.readonly)
|
|
194
|
-
return res.status(403).json({ success: false, error: 'Read-only mode — creating databases is disabled.' });
|
|
195
|
-
const name = String(req.body?.name ?? '').trim();
|
|
196
|
-
if (!name)
|
|
197
|
-
return res.status(400).json({ success: false, error: 'Database name is required.' });
|
|
198
|
-
const id = manager.create(name);
|
|
199
|
-
res.status(201).json({ success: true, data: { id, message: `Database "${id}" created.` } });
|
|
200
|
-
}));
|
|
201
|
-
router.delete('/api/databases/:id', wrap(async (req, res) => {
|
|
202
|
-
if (ctx.readonly)
|
|
203
|
-
return res.status(403).json({ success: false, error: 'Read-only mode — deleting databases is disabled.' });
|
|
204
|
-
const id = String(req.params.id);
|
|
205
|
-
if (!manager.has(id))
|
|
206
|
-
return res.status(404).json({ success: false, error: `Database "${id}" does not exist.` });
|
|
207
|
-
if (ctx.invalidate)
|
|
208
|
-
ctx.invalidate(id);
|
|
209
|
-
manager.remove(id);
|
|
210
|
-
res.json({ success: true, data: { message: `Database "${id}" removed.` } });
|
|
211
|
-
}));
|
|
212
|
-
}
|
package/dist/routes/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './pages';export * from './databases';export * from './api/index';
|
package/dist/routes/pages.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import type {Router,Request} from 'express';import type {IDatabase} from '../db/index';import type {Logger} from '../utils/logger';export interface PageContext{getDb:(req:Request)=>IDatabase;logger:Logger;}export declare function registerPages(router:Router,ctx:PageContext):void;
|