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
package/dist/routes/pages.js
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerPages = registerPages;
|
|
4
|
-
const export_1 = require("../db/export");
|
|
5
|
-
const generator_1 = require("../sql/generator");
|
|
6
|
-
const common_1 = require("../utils/common");
|
|
7
|
-
const datatype_1 = require("../utils/datatype");
|
|
8
|
-
const index_1 = require("../data/index");
|
|
9
|
-
function buildDisplayRows(rawRows, info, dbId, table, basePath = '') {
|
|
10
|
-
const pkCols = info.primaryKey.length ? info.primaryKey : ['_rowid_'];
|
|
11
|
-
return rawRows.map((row) => {
|
|
12
|
-
const pkEncoded = (0, common_1.encodePk)(pkCols.map((c) => row[c]));
|
|
13
|
-
return {
|
|
14
|
-
cells: info.columns.map((c) => {
|
|
15
|
-
const raw = row[c.name];
|
|
16
|
-
const v = (0, common_1.normalizeCell)(raw);
|
|
17
|
-
const isNull = v == null;
|
|
18
|
-
const typeUpper = (c.type || '').toUpperCase();
|
|
19
|
-
const isBlob = typeUpper.includes('BLOB') || typeUpper.includes('BYTEA') || Buffer.isBuffer(raw) || raw instanceof Uint8Array || (typeof v === 'string' && (/^0x[0-9a-f]{8,}$/i.test(v) || /^\\x[0-9a-f]{8,}$/i.test(v)));
|
|
20
|
-
let isJson = false;
|
|
21
|
-
let isImage = false;
|
|
22
|
-
let isUrl = false;
|
|
23
|
-
let isColor = false;
|
|
24
|
-
let isAudio = false;
|
|
25
|
-
let isVideo = false;
|
|
26
|
-
let isPdf = false;
|
|
27
|
-
let blobSize = '';
|
|
28
|
-
let blobMime = '';
|
|
29
|
-
let blobExt = '';
|
|
30
|
-
let blobUrl = '';
|
|
31
|
-
if (!isNull) {
|
|
32
|
-
const str = String(v);
|
|
33
|
-
if (isBlob) {
|
|
34
|
-
let buf;
|
|
35
|
-
if (Buffer.isBuffer(raw) || raw instanceof Uint8Array) {
|
|
36
|
-
buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
37
|
-
}
|
|
38
|
-
else if (typeof v === 'string' && (/^0x[0-9a-f]*$/i.test(v) || /^\\x[0-9a-f]*$/i.test(v))) {
|
|
39
|
-
buf = Buffer.from(v.slice(2), 'hex');
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
buf = Buffer.from(str, 'utf8');
|
|
43
|
-
}
|
|
44
|
-
const mimeInfo = (0, datatype_1.sniffMimeType)(buf);
|
|
45
|
-
blobSize = (0, common_1.formatBytes)(buf.length);
|
|
46
|
-
blobMime = mimeInfo.mime;
|
|
47
|
-
blobExt = mimeInfo.ext;
|
|
48
|
-
isImage = mimeInfo.isImage;
|
|
49
|
-
isAudio = mimeInfo.isAudio;
|
|
50
|
-
isVideo = mimeInfo.isVideo;
|
|
51
|
-
isPdf = mimeInfo.isPdf;
|
|
52
|
-
if (table && pkEncoded && dbId) {
|
|
53
|
-
blobUrl = `${basePath}/api/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}/row/${encodeURIComponent(pkEncoded)}/blob/${encodeURIComponent(c.name)}`;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
else if ((0, datatype_1.isJsonString)(v) || typeUpper.includes('JSON')) {
|
|
57
|
-
isJson = (0, datatype_1.isJsonString)(v);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
if (/^https?:\/\/[^\s$.?#].[^\s]*$/i.test(str))
|
|
61
|
-
isUrl = true;
|
|
62
|
-
else if (/^#(?:[0-9a-fA-F]{3}){1,2}$|^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+/i.test(str))
|
|
63
|
-
isColor = true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
name: c.name,
|
|
68
|
-
value: v,
|
|
69
|
-
isNull,
|
|
70
|
-
display: isNull ? '' : String(v),
|
|
71
|
-
type: c.type,
|
|
72
|
-
isBlob,
|
|
73
|
-
isJson,
|
|
74
|
-
isImage,
|
|
75
|
-
isAudio,
|
|
76
|
-
isVideo,
|
|
77
|
-
isPdf,
|
|
78
|
-
isUrl,
|
|
79
|
-
isColor,
|
|
80
|
-
blobSize,
|
|
81
|
-
blobMime,
|
|
82
|
-
blobExt,
|
|
83
|
-
blobUrl,
|
|
84
|
-
};
|
|
85
|
-
}),
|
|
86
|
-
pkEncoded,
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
function initPageLocals(res, db, dbId, allNames) {
|
|
91
|
-
const tables = allNames.filter((n) => !n.startsWith('_'));
|
|
92
|
-
const internalTables = allNames.filter((n) => n.startsWith('_'));
|
|
93
|
-
res.locals.dbId = dbId;
|
|
94
|
-
res.locals.dbPath = db.path;
|
|
95
|
-
res.locals.tables = tables;
|
|
96
|
-
res.locals.internalTables = internalTables;
|
|
97
|
-
res.locals.dialect = db.dialect;
|
|
98
|
-
res.locals.isPostgres = db.dialect === 'postgres';
|
|
99
|
-
res.locals.isSqlite = db.dialect === 'sqlite';
|
|
100
|
-
res.locals.dialectName = db.dialect === 'postgres' ? 'PostgreSQL' : 'SQLite';
|
|
101
|
-
res.locals.readonly = db.isReadOnly;
|
|
102
|
-
res.locals.databasesMode = false;
|
|
103
|
-
return { tables, internalTables };
|
|
104
|
-
}
|
|
105
|
-
function registerPages(router, ctx) {
|
|
106
|
-
const notFound = (res, message) => {
|
|
107
|
-
res.status(404).render('pages/error', { title: 'Not found', status: 404, error: message });
|
|
108
|
-
};
|
|
109
|
-
router.get('/home/:db', async (req, res, next) => {
|
|
110
|
-
try {
|
|
111
|
-
const db = ctx.getDb(req);
|
|
112
|
-
const dbId = req.params.db;
|
|
113
|
-
const all = await db.listTables();
|
|
114
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
115
|
-
const { tables: names, internalTables: internalNames } = initPageLocals(res, db, dbId, allNames);
|
|
116
|
-
const colCountsMap = {};
|
|
117
|
-
if (db.dialect === 'postgres') {
|
|
118
|
-
const colRes = await db.all(`SELECT table_name, count(*)::int AS cols FROM information_schema.columns WHERE table_schema = 'public' GROUP BY table_name;`);
|
|
119
|
-
if (colRes.success && colRes.data) {
|
|
120
|
-
for (const row of colRes.data) {
|
|
121
|
-
if (row && typeof row.table_name === 'string') {
|
|
122
|
-
colCountsMap[row.table_name] = Number(row.cols ?? 0);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const [stats, saved] = await Promise.all([
|
|
128
|
-
Promise.all(names.map(async (name) => {
|
|
129
|
-
const countR = await db.getRowCount(name);
|
|
130
|
-
const cols = colCountsMap[name] !== undefined
|
|
131
|
-
? colCountsMap[name]
|
|
132
|
-
: (await db.getTableInfo(name)).data?.columns.length ?? 0;
|
|
133
|
-
return {
|
|
134
|
-
name,
|
|
135
|
-
count: countR.success ? countR.data : 0,
|
|
136
|
-
cols,
|
|
137
|
-
};
|
|
138
|
-
})),
|
|
139
|
-
db.listSavedQueries(),
|
|
140
|
-
]);
|
|
141
|
-
const counts = {};
|
|
142
|
-
const cols = {};
|
|
143
|
-
let totalRows = 0;
|
|
144
|
-
for (const s of stats) {
|
|
145
|
-
counts[s.name] = s.count;
|
|
146
|
-
cols[s.name] = s.cols;
|
|
147
|
-
totalRows += s.count;
|
|
148
|
-
}
|
|
149
|
-
res.render('pages/home', {
|
|
150
|
-
title: 'Home',
|
|
151
|
-
dbId,
|
|
152
|
-
tables: names,
|
|
153
|
-
counts,
|
|
154
|
-
cols,
|
|
155
|
-
totalRows,
|
|
156
|
-
savedQueries: saved.success ? (saved.data?.length ?? 0) : 0,
|
|
157
|
-
internalCount: internalNames.length,
|
|
158
|
-
dbPath: db.path,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
next(err);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
router.get('/tables/:db/:table', async (req, res, next) => {
|
|
166
|
-
try {
|
|
167
|
-
const db = ctx.getDb(req);
|
|
168
|
-
const dbId = req.params.db;
|
|
169
|
-
const table = req.params.table;
|
|
170
|
-
res.locals.dbId = dbId;
|
|
171
|
-
const all = await db.listTables();
|
|
172
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
173
|
-
initPageLocals(res, db, dbId, allNames);
|
|
174
|
-
const info = await db.getTableInfo(table);
|
|
175
|
-
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
176
|
-
return notFound(res, `Table "${table}" does not exist.`);
|
|
177
|
-
}
|
|
178
|
-
const page = Math.max(1, Number.parseInt(String(req.query.page ?? '1'), 10) || 1);
|
|
179
|
-
const pageSize = Math.max(1, Number.parseInt(String(req.query.size ?? '50'), 10) || 50);
|
|
180
|
-
const orderBy = req.query.orderBy ? String(req.query.orderBy) : undefined;
|
|
181
|
-
const orderDir = String(req.query.orderDir ?? 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc';
|
|
182
|
-
const filters = (0, common_1.parseFilters)(req.query.f);
|
|
183
|
-
const filterQS = (0, common_1.filtersToQS)(filters);
|
|
184
|
-
const [countR, rowsR, refsR] = await Promise.all([
|
|
185
|
-
db.getRowCount(table, filters),
|
|
186
|
-
db.getRows(table, { page, limit: pageSize, orderBy, orderDir, filters }),
|
|
187
|
-
db.getReferencingTables(table),
|
|
188
|
-
]);
|
|
189
|
-
const count = countR.success ? countR.data : 0;
|
|
190
|
-
const rawRows = (rowsR.data ?? []);
|
|
191
|
-
const pages = Math.max(1, Math.ceil(count / pageSize));
|
|
192
|
-
const basePath = String(res.locals.basePath ?? '');
|
|
193
|
-
const sizes = [25, 50, 100, 250];
|
|
194
|
-
const colHeaders = info.data.columns.map((c) => {
|
|
195
|
-
const active = c.name === orderBy;
|
|
196
|
-
const nextDir = active ? (orderDir === 'asc' ? 'desc' : 'asc') : 'asc';
|
|
197
|
-
const qs = `page=1&size=${pageSize}&orderBy=${encodeURIComponent(c.name)}&orderDir=${nextDir}`;
|
|
198
|
-
return {
|
|
199
|
-
name: c.name,
|
|
200
|
-
pk: c.pk,
|
|
201
|
-
type: c.type,
|
|
202
|
-
active,
|
|
203
|
-
dir: active ? orderDir : null,
|
|
204
|
-
href: `${basePath}/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}?${qs}${filterQS ? `&${filterQS}` : ''}`,
|
|
205
|
-
};
|
|
206
|
-
});
|
|
207
|
-
const refColumns = (refsR.data ?? [])
|
|
208
|
-
.filter((r) => !r.table.startsWith('_'))
|
|
209
|
-
.map((rt) => ({ table: rt.table, from: rt.refs[0]?.from ?? '', to: rt.refs[0]?.to ?? '' }));
|
|
210
|
-
const countEntries = await Promise.all(refColumns.map(async (col) => {
|
|
211
|
-
const values = rawRows
|
|
212
|
-
.map((r) => r[col.to])
|
|
213
|
-
.filter((v) => v !== null && v !== undefined);
|
|
214
|
-
if (!values.length)
|
|
215
|
-
return [col.table, new Map()];
|
|
216
|
-
const placeholders = values.map(() => '?').join(', ');
|
|
217
|
-
const cR = await db.all(`SELECT ${(0, generator_1.quoteIdentifier)(col.from)} AS fk, COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(col.table)} WHERE ${(0, generator_1.quoteIdentifier)(col.from)} IN (${placeholders}) GROUP BY ${(0, generator_1.quoteIdentifier)(col.from)}`, values);
|
|
218
|
-
const map = new Map();
|
|
219
|
-
for (const row of (cR.data ?? []))
|
|
220
|
-
map.set(String(row.fk), Number(row.c));
|
|
221
|
-
return [col.table, map];
|
|
222
|
-
}));
|
|
223
|
-
const countMap = new Map(countEntries);
|
|
224
|
-
const rows = buildDisplayRows(rawRows, info.data, dbId, table, basePath).map((dr, i) => {
|
|
225
|
-
const raw = rawRows[i];
|
|
226
|
-
const refs = refColumns.map((col) => {
|
|
227
|
-
const v = raw[col.to];
|
|
228
|
-
return {
|
|
229
|
-
table: col.table,
|
|
230
|
-
from: col.from,
|
|
231
|
-
to: col.to,
|
|
232
|
-
value: v == null ? '' : String(v),
|
|
233
|
-
count: v == null ? 0 : (countMap.get(col.table)?.get(String(v)) ?? 0),
|
|
234
|
-
};
|
|
235
|
-
});
|
|
236
|
-
return { ...dr, refs };
|
|
237
|
-
});
|
|
238
|
-
res.locals.currentTable = table;
|
|
239
|
-
res.render('pages/table', {
|
|
240
|
-
title: table,
|
|
241
|
-
dbId,
|
|
242
|
-
table,
|
|
243
|
-
info: info.data,
|
|
244
|
-
rows,
|
|
245
|
-
count,
|
|
246
|
-
page,
|
|
247
|
-
pageSize,
|
|
248
|
-
pages,
|
|
249
|
-
pkCols: info.data.primaryKey,
|
|
250
|
-
colNames: info.data.columns.map((c) => c.name),
|
|
251
|
-
colHeaders,
|
|
252
|
-
refColumns,
|
|
253
|
-
hasPk: true,
|
|
254
|
-
orderBy: orderBy ?? '',
|
|
255
|
-
orderDir,
|
|
256
|
-
sizes,
|
|
257
|
-
filters,
|
|
258
|
-
filterQS,
|
|
259
|
-
firstRow: count === 0 ? 0 : (page - 1) * pageSize + 1,
|
|
260
|
-
lastRow: Math.min(page * pageSize, count),
|
|
261
|
-
browseConfig: {
|
|
262
|
-
dbId,
|
|
263
|
-
table,
|
|
264
|
-
filters,
|
|
265
|
-
pkCols: info.data.primaryKey.length ? info.data.primaryKey : ['_rowid_'],
|
|
266
|
-
hasPk: true,
|
|
267
|
-
readonly: db.isReadOnly,
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
catch (err) {
|
|
272
|
-
next(err);
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
router.get('/schema/:db/:table', async (req, res, next) => {
|
|
276
|
-
try {
|
|
277
|
-
const db = ctx.getDb(req);
|
|
278
|
-
const dbId = req.params.db;
|
|
279
|
-
const table = req.params.table;
|
|
280
|
-
res.locals.dbId = dbId;
|
|
281
|
-
const all = await db.listTables();
|
|
282
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
283
|
-
initPageLocals(res, db, dbId, allNames);
|
|
284
|
-
const [info, schema] = await Promise.all([db.getTableInfo(table), db.getSchema(table)]);
|
|
285
|
-
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
286
|
-
return notFound(res, `Table "${table}" does not exist.`);
|
|
287
|
-
}
|
|
288
|
-
if (!schema.success || !schema.data) {
|
|
289
|
-
return notFound(res, schema.error ?? 'Failed to load schema.');
|
|
290
|
-
}
|
|
291
|
-
res.locals.currentTable = table;
|
|
292
|
-
res.render('pages/schema', {
|
|
293
|
-
title: `Schema · ${table}`,
|
|
294
|
-
dbId,
|
|
295
|
-
table,
|
|
296
|
-
schema: schema.data,
|
|
297
|
-
isInternal: table.startsWith('_'),
|
|
298
|
-
dialect: db.dialect,
|
|
299
|
-
isSqlite: db.dialect === 'sqlite',
|
|
300
|
-
isPostgres: db.dialect === 'postgres',
|
|
301
|
-
config: { dbId, table, isInternal: table.startsWith('_'), dialect: db.dialect },
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
catch (err) {
|
|
305
|
-
next(err);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
router.get('/seed/:db', async (req, res, next) => {
|
|
309
|
-
try {
|
|
310
|
-
const db = ctx.getDb(req);
|
|
311
|
-
const dbId = req.params.db;
|
|
312
|
-
res.locals.dbId = dbId;
|
|
313
|
-
const all = await db.listTables();
|
|
314
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
315
|
-
const { tables: tableNames } = initPageLocals(res, db, dbId, allNames);
|
|
316
|
-
if (tableNames.length === 0) {
|
|
317
|
-
return res.render('pages/seed-select', {
|
|
318
|
-
title: 'Seed Data Generator',
|
|
319
|
-
dbId,
|
|
320
|
-
tables: tableNames,
|
|
321
|
-
seedTables: [],
|
|
322
|
-
hasTables: false,
|
|
323
|
-
dialect: db.dialect,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
const tableStats = await Promise.all(tableNames.map(async (name) => {
|
|
327
|
-
const [info, countRes] = await Promise.all([db.getTableInfo(name), db.getRowCount(name)]);
|
|
328
|
-
const cols = info.success && info.data ? info.data.columns.length : 0;
|
|
329
|
-
const fks = info.success && info.data ? info.data.foreignKeys.length : 0;
|
|
330
|
-
const rowCount = countRes.success ? countRes.data : 0;
|
|
331
|
-
return {
|
|
332
|
-
name,
|
|
333
|
-
cols,
|
|
334
|
-
fks,
|
|
335
|
-
rowCount,
|
|
336
|
-
};
|
|
337
|
-
}));
|
|
338
|
-
const defaultTable = req.query.table ? String(req.query.table) : tableStats[0]?.name;
|
|
339
|
-
const defaultMode = req.query.mode === 'chain' ? 'chain' : 'single';
|
|
340
|
-
res.render('pages/seed-select', {
|
|
341
|
-
title: 'Seed Data Generator · Choose Table & Mode',
|
|
342
|
-
dbId,
|
|
343
|
-
tables: tableNames,
|
|
344
|
-
seedTables: tableStats,
|
|
345
|
-
hasTables: true,
|
|
346
|
-
defaultTable,
|
|
347
|
-
defaultMode,
|
|
348
|
-
dialect: db.dialect,
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
catch (err) {
|
|
352
|
-
next(err);
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
router.get('/seed/:db/:table', async (req, res, next) => {
|
|
356
|
-
try {
|
|
357
|
-
const db = ctx.getDb(req);
|
|
358
|
-
const dbId = req.params.db;
|
|
359
|
-
const table = req.params.table;
|
|
360
|
-
res.locals.dbId = dbId;
|
|
361
|
-
const all = await db.listTables();
|
|
362
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
363
|
-
const { tables: allTables } = initPageLocals(res, db, dbId, allNames);
|
|
364
|
-
const initialMode = req.query.mode === 'chain' ? 'chain' : 'single';
|
|
365
|
-
const [info, countRes] = await Promise.all([db.getTableInfo(table), db.getRowCount(table)]);
|
|
366
|
-
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
367
|
-
return notFound(res, `Table "${table}" does not exist.`);
|
|
368
|
-
}
|
|
369
|
-
const rowCount = countRes.success ? countRes.data : 0;
|
|
370
|
-
const columns = (0, index_1.buildColumnConfigs)(info.data);
|
|
371
|
-
res.locals.currentTable = table;
|
|
372
|
-
res.render('pages/seed', {
|
|
373
|
-
title: `Seed data · ${table}`,
|
|
374
|
-
dbId,
|
|
375
|
-
table,
|
|
376
|
-
allTables,
|
|
377
|
-
mode: initialMode,
|
|
378
|
-
isChainMode: initialMode === 'chain',
|
|
379
|
-
rowCount,
|
|
380
|
-
colCount: info.data.columns.length,
|
|
381
|
-
maxRows: index_1.MAX_SEED_ROWS,
|
|
382
|
-
quickCounts: [10, 50, 100, 500, 1000, index_1.MAX_SEED_ROWS],
|
|
383
|
-
seedConfig: { dbId, table, columns, rowCount, maxRows: index_1.MAX_SEED_ROWS, mode: initialMode },
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
catch (err) {
|
|
387
|
-
next(err);
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
router.get('/tables/:db/:table/rows/new', async (req, res, next) => {
|
|
391
|
-
try {
|
|
392
|
-
const db = ctx.getDb(req);
|
|
393
|
-
const dbId = req.params.db;
|
|
394
|
-
const table = req.params.table;
|
|
395
|
-
res.locals.dbId = dbId;
|
|
396
|
-
const all = await db.listTables();
|
|
397
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
398
|
-
initPageLocals(res, db, dbId, allNames);
|
|
399
|
-
const info = await db.getTableInfo(table);
|
|
400
|
-
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
401
|
-
return notFound(res, `Table "${table}" does not exist.`);
|
|
402
|
-
}
|
|
403
|
-
const pkColumns = info.data.primaryKey;
|
|
404
|
-
res.locals.currentTable = table;
|
|
405
|
-
res.render('pages/form', {
|
|
406
|
-
title: `New row · ${table}`,
|
|
407
|
-
dbId,
|
|
408
|
-
table,
|
|
409
|
-
mode: 'insert',
|
|
410
|
-
pk: null,
|
|
411
|
-
pkColumns,
|
|
412
|
-
infoSummary: `${info.data.columns.length} column(s)` + (pkColumns.length ? ` · PK: ${pkColumns.join(', ')}` : ''),
|
|
413
|
-
config: { dbId, table, mode: 'insert', pk: null, pkColumns },
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
catch (err) {
|
|
417
|
-
next(err);
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
router.get('/tables/:db/:table/rows/:id/edit', async (req, res, next) => {
|
|
421
|
-
try {
|
|
422
|
-
const db = ctx.getDb(req);
|
|
423
|
-
const dbId = req.params.db;
|
|
424
|
-
const table = req.params.table;
|
|
425
|
-
res.locals.dbId = dbId;
|
|
426
|
-
const all = await db.listTables();
|
|
427
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
428
|
-
initPageLocals(res, db, dbId, allNames);
|
|
429
|
-
const info = await db.getTableInfo(table);
|
|
430
|
-
if (!info.success || !info.data || info.data.columns.length === 0) {
|
|
431
|
-
return notFound(res, `Table "${table}" does not exist.`);
|
|
432
|
-
}
|
|
433
|
-
const pkColumns = info.data.primaryKey.length ? info.data.primaryKey : ['_rowid_'];
|
|
434
|
-
const pk = (0, common_1.decodePk)(req.params.id);
|
|
435
|
-
if (pk.length !== pkColumns.length) {
|
|
436
|
-
return notFound(res, 'Invalid row identifier.');
|
|
437
|
-
}
|
|
438
|
-
res.locals.currentTable = table;
|
|
439
|
-
res.render('pages/form', {
|
|
440
|
-
title: `Edit row · ${table}`,
|
|
441
|
-
dbId,
|
|
442
|
-
table,
|
|
443
|
-
mode: 'edit',
|
|
444
|
-
pk: req.params.id,
|
|
445
|
-
pkColumns,
|
|
446
|
-
infoSummary: `${info.data.columns.length} column(s)` + (info.data.primaryKey.length ? ` · PK: ${info.data.primaryKey.join(', ')}` : ''),
|
|
447
|
-
config: { dbId, table, mode: 'edit', pk: req.params.id, pkColumns },
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
catch (err) {
|
|
451
|
-
next(err);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
router.get('/erd/:db', async (req, res, next) => {
|
|
455
|
-
try {
|
|
456
|
-
const db = ctx.getDb(req);
|
|
457
|
-
const dbId = req.params.db;
|
|
458
|
-
res.locals.dbId = dbId;
|
|
459
|
-
const all = await db.listTables();
|
|
460
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
461
|
-
initPageLocals(res, db, dbId, allNames);
|
|
462
|
-
res.render('pages/erd', { title: 'ER Diagram', dbId });
|
|
463
|
-
}
|
|
464
|
-
catch (err) {
|
|
465
|
-
next(err);
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
router.get('/designer/:db', async (req, res, next) => {
|
|
469
|
-
try {
|
|
470
|
-
const db = ctx.getDb(req);
|
|
471
|
-
const dbId = req.params.db;
|
|
472
|
-
res.locals.dbId = dbId;
|
|
473
|
-
const all = await db.listTables();
|
|
474
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
475
|
-
initPageLocals(res, db, dbId, allNames);
|
|
476
|
-
res.render('pages/designer', {
|
|
477
|
-
title: 'New table',
|
|
478
|
-
dbId,
|
|
479
|
-
dialect: db.dialect,
|
|
480
|
-
isSqlite: db.dialect === 'sqlite',
|
|
481
|
-
isPostgres: db.dialect === 'postgres',
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
catch (err) {
|
|
485
|
-
next(err);
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
router.get('/query/:db', async (req, res, next) => {
|
|
489
|
-
try {
|
|
490
|
-
const db = ctx.getDb(req);
|
|
491
|
-
const dbId = req.params.db;
|
|
492
|
-
res.locals.dbId = dbId;
|
|
493
|
-
const all = await db.listTables();
|
|
494
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
495
|
-
initPageLocals(res, db, dbId, allNames);
|
|
496
|
-
const queries = await db.listSavedQueries();
|
|
497
|
-
res.render('pages/query', { title: 'Query editor', dbId, savedQueries: queries.success ? queries.data : [] });
|
|
498
|
-
}
|
|
499
|
-
catch (err) {
|
|
500
|
-
next(err);
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
router.get('/export/:db', async (req, res, next) => {
|
|
504
|
-
try {
|
|
505
|
-
const db = ctx.getDb(req);
|
|
506
|
-
const dbId = req.params.db;
|
|
507
|
-
const dump = await (0, export_1.generateSqlDump)(db);
|
|
508
|
-
if (!dump.success || !dump.data)
|
|
509
|
-
throw new Error(dump.error ?? 'Failed to generate dump.');
|
|
510
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
511
|
-
res.setHeader('Content-Type', 'application/sql; charset=utf-8');
|
|
512
|
-
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dbId)}-${stamp}.sql"`);
|
|
513
|
-
res.send(dump.data);
|
|
514
|
-
}
|
|
515
|
-
catch (err) {
|
|
516
|
-
next(err);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
router.get('/info/:db', async (req, res, next) => {
|
|
520
|
-
try {
|
|
521
|
-
const db = ctx.getDb(req);
|
|
522
|
-
const dbId = req.params.db;
|
|
523
|
-
res.locals.dbId = dbId;
|
|
524
|
-
const all = await db.listTables();
|
|
525
|
-
const allNames = (all.data ?? []).map((t) => t.name);
|
|
526
|
-
initPageLocals(res, db, dbId, allNames);
|
|
527
|
-
const settingsRes = await db.getSettings();
|
|
528
|
-
res.render('pages/info', {
|
|
529
|
-
title: 'Database Info',
|
|
530
|
-
dbId,
|
|
531
|
-
settings: settingsRes.success ? settingsRes.data : {},
|
|
532
|
-
dialect: db.dialect,
|
|
533
|
-
dbPath: db.path,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
catch (err) {
|
|
537
|
-
next(err);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
}
|