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