mysql-dashboard 0.1.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/DEVELOPMENT.md +32 -0
- package/README.md +37 -0
- package/lib/index.js +927 -0
- package/output/index.html +13 -0
- package/output/static/index.css +1458 -0
- package/output/static/index.css.map +1 -0
- package/output/static/index.js +444 -0
- package/output/static/index.js.map +7 -0
- package/package.json +48 -0
- package/start.mjs +40 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// server/api/connection.ts
|
|
26
|
+
var import_f2e_server32 = require("f2e-server3");
|
|
27
|
+
|
|
28
|
+
// server/dao/connection.ts
|
|
29
|
+
var import_f2e_server3 = require("f2e-server3");
|
|
30
|
+
var import_fs = __toESM(require("fs"));
|
|
31
|
+
var appStorage = new import_f2e_server3.DBFile({
|
|
32
|
+
filePath: ".f2e_cache/mysql-connection.json",
|
|
33
|
+
initData: {
|
|
34
|
+
connections: []
|
|
35
|
+
},
|
|
36
|
+
fileToData(filePath) {
|
|
37
|
+
return JSON.parse(import_fs.default.readFileSync(filePath, "utf8"));
|
|
38
|
+
},
|
|
39
|
+
dataToFile(data) {
|
|
40
|
+
return JSON.stringify(data, null, 2);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// server/utils/id.ts
|
|
45
|
+
function generateId() {
|
|
46
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// server/utils/mysql.ts
|
|
50
|
+
var import_promise = __toESM(require("mysql2/promise"));
|
|
51
|
+
function buildConnectionOptions(connection) {
|
|
52
|
+
const uri = connection.connectionString?.trim();
|
|
53
|
+
if (uri) {
|
|
54
|
+
return uri;
|
|
55
|
+
}
|
|
56
|
+
const { host, port = 3306, username, password, database } = connection;
|
|
57
|
+
return {
|
|
58
|
+
host,
|
|
59
|
+
port,
|
|
60
|
+
user: username || void 0,
|
|
61
|
+
password: password || void 0,
|
|
62
|
+
database: database || void 0,
|
|
63
|
+
connectTimeout: 15e3,
|
|
64
|
+
supportBigNumbers: true,
|
|
65
|
+
bigNumberStrings: true,
|
|
66
|
+
dateStrings: true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function createMysqlConnection(connection) {
|
|
70
|
+
const opts = buildConnectionOptions(connection);
|
|
71
|
+
const conn = typeof opts === "string" ? await import_promise.default.createConnection(opts) : await import_promise.default.createConnection(opts);
|
|
72
|
+
return conn;
|
|
73
|
+
}
|
|
74
|
+
async function testConnection(connection) {
|
|
75
|
+
let conn = null;
|
|
76
|
+
try {
|
|
77
|
+
conn = await createMysqlConnection(connection);
|
|
78
|
+
await conn.ping();
|
|
79
|
+
return { success: true, message: "\u8FDE\u63A5\u6210\u529F" };
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
message: error instanceof Error ? error.message : "\u8FDE\u63A5\u5931\u8D25"
|
|
84
|
+
};
|
|
85
|
+
} finally {
|
|
86
|
+
if (conn) {
|
|
87
|
+
await conn.end();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function withMysqlConnection(connection, callback) {
|
|
92
|
+
const conn = await createMysqlConnection(connection);
|
|
93
|
+
try {
|
|
94
|
+
return await callback(conn);
|
|
95
|
+
} finally {
|
|
96
|
+
await conn.end();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function escapeIdentifier(name) {
|
|
100
|
+
return import_promise.default.escapeId(name);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// server/api/connection.ts
|
|
104
|
+
(0, import_f2e_server32.addRoute)("/api/connection/list", async () => {
|
|
105
|
+
const connections = appStorage.data.connections || [];
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
data: connections,
|
|
109
|
+
message: "\u83B7\u53D6\u8FDE\u63A5\u5217\u8868\u6210\u529F"
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
(0, import_f2e_server32.addRoute)("/api/connection/create", async (connection) => {
|
|
113
|
+
const connections = appStorage.data.connections || [];
|
|
114
|
+
const newConnection = {
|
|
115
|
+
...connection,
|
|
116
|
+
_id: generateId(),
|
|
117
|
+
createdAt: Date.now(),
|
|
118
|
+
updatedAt: Date.now()
|
|
119
|
+
};
|
|
120
|
+
connections.push(newConnection);
|
|
121
|
+
appStorage.update();
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
data: newConnection,
|
|
125
|
+
message: "\u521B\u5EFA\u8FDE\u63A5\u6210\u529F"
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
(0, import_f2e_server32.addRoute)("/api/connection/update/:_id", async (connection, ctx) => {
|
|
129
|
+
const connections = appStorage.data.connections || [];
|
|
130
|
+
const id = ctx.params._id;
|
|
131
|
+
const index = connections.findIndex((c) => c._id === id);
|
|
132
|
+
if (index === -1) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
connections[index] = {
|
|
139
|
+
...connections[index],
|
|
140
|
+
...connection,
|
|
141
|
+
_id: id,
|
|
142
|
+
updatedAt: Date.now()
|
|
143
|
+
};
|
|
144
|
+
appStorage.update();
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
data: connections[index],
|
|
148
|
+
message: "\u66F4\u65B0\u8FDE\u63A5\u6210\u529F"
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
(0, import_f2e_server32.addRoute)("/api/connection/delete/:_id", async (_body, ctx) => {
|
|
152
|
+
const connections = appStorage.data.connections || [];
|
|
153
|
+
const id = ctx.params._id;
|
|
154
|
+
const index = connections.findIndex((c) => c._id === id);
|
|
155
|
+
if (index === -1) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
connections.splice(index, 1);
|
|
162
|
+
appStorage.update();
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
message: "\u5220\u9664\u8FDE\u63A5\u6210\u529F"
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
(0, import_f2e_server32.addRoute)("/api/connection/test", async (connection) => {
|
|
169
|
+
const result = await testConnection(connection);
|
|
170
|
+
return {
|
|
171
|
+
success: result.success,
|
|
172
|
+
data: result,
|
|
173
|
+
message: result.message
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// server/api/database.ts
|
|
178
|
+
var import_f2e_server33 = require("f2e-server3");
|
|
179
|
+
|
|
180
|
+
// server/utils/database.ts
|
|
181
|
+
var RESERVED = ["mysql", "information_schema", "performance_schema", "sys"];
|
|
182
|
+
function isReadOnlyDatabase(databaseName) {
|
|
183
|
+
return RESERVED.includes(databaseName.toLowerCase());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// server/api/database.ts
|
|
187
|
+
function assertIdent(name, label) {
|
|
188
|
+
if (!/^[a-zA-Z0-9_]+$/.test(name)) {
|
|
189
|
+
throw new Error(`${label}\u540D\u79F0\u5305\u542B\u975E\u6CD5\u5B57\u7B26`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
(0, import_f2e_server33.addRoute)("/api/database/list", async (body) => {
|
|
193
|
+
const connections = appStorage.data.connections || [];
|
|
194
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
195
|
+
if (!connection) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const databases = await withMysqlConnection(connection, async (conn) => {
|
|
203
|
+
const [rows] = await conn.query(
|
|
204
|
+
`SELECT s.SCHEMA_NAME AS name,
|
|
205
|
+
COALESCE(SUM(t.DATA_LENGTH + t.INDEX_LENGTH), 0) AS sizeOnDisk
|
|
206
|
+
FROM information_schema.SCHEMATA s
|
|
207
|
+
LEFT JOIN information_schema.TABLES t ON t.TABLE_SCHEMA = s.SCHEMA_NAME
|
|
208
|
+
GROUP BY s.SCHEMA_NAME
|
|
209
|
+
ORDER BY s.SCHEMA_NAME`
|
|
210
|
+
);
|
|
211
|
+
return rows.map((d) => {
|
|
212
|
+
const size = Number(d.sizeOnDisk) || 0;
|
|
213
|
+
return {
|
|
214
|
+
name: d.name,
|
|
215
|
+
sizeOnDisk: size,
|
|
216
|
+
empty: size === 0
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
data: databases,
|
|
223
|
+
message: "\u83B7\u53D6\u6570\u636E\u5E93\u5217\u8868\u6210\u529F"
|
|
224
|
+
};
|
|
225
|
+
} catch (error) {
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
message: error instanceof Error ? error.message : "\u83B7\u53D6\u6570\u636E\u5E93\u5217\u8868\u5931\u8D25"
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
(0, import_f2e_server33.addRoute)("/api/database/create", async (body) => {
|
|
233
|
+
const connections = appStorage.data.connections || [];
|
|
234
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
235
|
+
if (!connection) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
assertIdent(body.databaseName, "\u6570\u636E\u5E93");
|
|
243
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
244
|
+
await conn.query(`CREATE DATABASE ${escapeIdentifier(body.databaseName)}`);
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
message: "\u521B\u5EFA\u6570\u636E\u5E93\u6210\u529F"
|
|
249
|
+
};
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return {
|
|
252
|
+
success: false,
|
|
253
|
+
message: error instanceof Error ? error.message : "\u521B\u5EFA\u6570\u636E\u5E93\u5931\u8D25"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
(0, import_f2e_server33.addRoute)("/api/database/delete", async (body) => {
|
|
258
|
+
const connections = appStorage.data.connections || [];
|
|
259
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
260
|
+
if (!connection) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
message: "\u4E0D\u80FD\u5220\u9664\u7CFB\u7EDF\u5E93\uFF08mysql\u3001information_schema\u3001performance_schema\u3001sys\uFF09"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
assertIdent(body.databaseName, "\u6570\u636E\u5E93");
|
|
274
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
275
|
+
await conn.query(`DROP DATABASE ${escapeIdentifier(body.databaseName)}`);
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
success: true,
|
|
279
|
+
message: "\u5220\u9664\u6570\u636E\u5E93\u6210\u529F"
|
|
280
|
+
};
|
|
281
|
+
} catch (error) {
|
|
282
|
+
return {
|
|
283
|
+
success: false,
|
|
284
|
+
message: error instanceof Error ? error.message : "\u5220\u9664\u6570\u636E\u5E93\u5931\u8D25"
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
(0, import_f2e_server33.addRoute)("/api/database/rename", async (body) => {
|
|
289
|
+
const connections = appStorage.data.connections || [];
|
|
290
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
291
|
+
if (!connection) {
|
|
292
|
+
return {
|
|
293
|
+
success: false,
|
|
294
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (isReadOnlyDatabase(body.oldDatabaseName) || isReadOnlyDatabase(body.newDatabaseName)) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u91CD\u547D\u540D\u6216\u4F5C\u4E3A\u76EE\u6807\u540D"
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
assertIdent(body.oldDatabaseName, "\u6570\u636E\u5E93");
|
|
305
|
+
assertIdent(body.newDatabaseName, "\u6570\u636E\u5E93");
|
|
306
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
307
|
+
const [existing] = await conn.query(
|
|
308
|
+
"SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ? LIMIT 1",
|
|
309
|
+
[body.newDatabaseName]
|
|
310
|
+
);
|
|
311
|
+
if (existing.length > 0) {
|
|
312
|
+
throw new Error("\u76EE\u6807\u6570\u636E\u5E93\u5DF2\u5B58\u5728");
|
|
313
|
+
}
|
|
314
|
+
await conn.query(`CREATE DATABASE ${escapeIdentifier(body.newDatabaseName)}`);
|
|
315
|
+
const [tables] = await conn.query(
|
|
316
|
+
`SELECT TABLE_NAME AS name FROM information_schema.TABLES
|
|
317
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'`,
|
|
318
|
+
[body.oldDatabaseName]
|
|
319
|
+
);
|
|
320
|
+
const oldDb = escapeIdentifier(body.oldDatabaseName);
|
|
321
|
+
const newDb = escapeIdentifier(body.newDatabaseName);
|
|
322
|
+
for (const t of tables) {
|
|
323
|
+
const tn = escapeIdentifier(t.name);
|
|
324
|
+
await conn.query(`RENAME TABLE ${oldDb}.${tn} TO ${newDb}.${tn}`);
|
|
325
|
+
}
|
|
326
|
+
await conn.query(`DROP DATABASE ${oldDb}`);
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
success: true,
|
|
330
|
+
message: "\u91CD\u547D\u540D\u6570\u636E\u5E93\u6210\u529F"
|
|
331
|
+
};
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return {
|
|
334
|
+
success: false,
|
|
335
|
+
message: error instanceof Error ? error.message : "\u91CD\u547D\u540D\u6570\u636E\u5E93\u5931\u8D25"
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// server/api/collection.ts
|
|
341
|
+
var import_f2e_server34 = require("f2e-server3");
|
|
342
|
+
function assertIdent2(name, label) {
|
|
343
|
+
return;
|
|
344
|
+
if (!/^[a-zA-Z0-9_]+$/.test(name)) {
|
|
345
|
+
throw new Error(`${label}\u540D\u79F0\u5305\u542B\u975E\u6CD5\u5B57\u7B26`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async function getPrimaryKeyColumns(conn, databaseName, tableName) {
|
|
349
|
+
const [rows] = await conn.query(
|
|
350
|
+
`SELECT COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE
|
|
351
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND CONSTRAINT_NAME = 'PRIMARY'
|
|
352
|
+
ORDER BY ORDINAL_POSITION`,
|
|
353
|
+
[databaseName, tableName]
|
|
354
|
+
);
|
|
355
|
+
return rows.map((r) => r.COLUMN_NAME);
|
|
356
|
+
}
|
|
357
|
+
function attachRowKeys(row, pkCols) {
|
|
358
|
+
if (pkCols.length === 0) {
|
|
359
|
+
return row;
|
|
360
|
+
}
|
|
361
|
+
const __pk = {};
|
|
362
|
+
for (const c of pkCols) {
|
|
363
|
+
__pk[c] = row[c];
|
|
364
|
+
}
|
|
365
|
+
return { ...row, __pk };
|
|
366
|
+
}
|
|
367
|
+
function stripMetaFields(obj) {
|
|
368
|
+
const out = {};
|
|
369
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
370
|
+
if (k.startsWith("__")) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (/^[a-zA-Z0-9_]+$/.test(k)) {
|
|
374
|
+
out[k] = v;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
(0, import_f2e_server34.addRoute)("/api/collection/list", async (body) => {
|
|
380
|
+
const connections = appStorage.data.connections || [];
|
|
381
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
382
|
+
if (!connection) {
|
|
383
|
+
return {
|
|
384
|
+
success: false,
|
|
385
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
390
|
+
const collections = await withMysqlConnection(connection, async (conn) => {
|
|
391
|
+
const [rows] = await conn.query(
|
|
392
|
+
`SELECT TABLE_NAME AS name,
|
|
393
|
+
TABLE_ROWS AS approxRows,
|
|
394
|
+
DATA_LENGTH AS dataLength,
|
|
395
|
+
INDEX_LENGTH AS indexLength
|
|
396
|
+
FROM information_schema.TABLES
|
|
397
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'
|
|
398
|
+
ORDER BY TABLE_NAME`,
|
|
399
|
+
[body.databaseName]
|
|
400
|
+
);
|
|
401
|
+
return rows.map((r) => {
|
|
402
|
+
const approx = Number(r.approxRows) || 0;
|
|
403
|
+
const dataLen = Number(r.dataLength) || 0;
|
|
404
|
+
const idxLen = Number(r.indexLength) || 0;
|
|
405
|
+
return {
|
|
406
|
+
name: r.name,
|
|
407
|
+
type: "table",
|
|
408
|
+
count: approx,
|
|
409
|
+
size: dataLen + idxLen,
|
|
410
|
+
avgObjSize: approx > 0 ? Math.round(dataLen / approx) : 0,
|
|
411
|
+
storageSize: dataLen,
|
|
412
|
+
totalIndexSize: idxLen
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
success: true,
|
|
418
|
+
data: collections,
|
|
419
|
+
message: "\u83B7\u53D6\u6570\u636E\u8868\u5217\u8868\u6210\u529F"
|
|
420
|
+
};
|
|
421
|
+
} catch (error) {
|
|
422
|
+
return {
|
|
423
|
+
success: false,
|
|
424
|
+
message: error instanceof Error ? error.message : "\u83B7\u53D6\u6570\u636E\u8868\u5217\u8868\u5931\u8D25"
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
(0, import_f2e_server34.addRoute)(
|
|
429
|
+
"/api/collection/schema",
|
|
430
|
+
async (body) => {
|
|
431
|
+
const connections = appStorage.data.connections || [];
|
|
432
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
433
|
+
if (!connection) {
|
|
434
|
+
return {
|
|
435
|
+
success: false,
|
|
436
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
441
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
442
|
+
const columns = await withMysqlConnection(connection, async (conn) => {
|
|
443
|
+
const [colRows] = await conn.query(
|
|
444
|
+
`SELECT COLUMN_NAME, ORDINAL_POSITION, COLUMN_TYPE, DATA_TYPE,
|
|
445
|
+
IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, EXTRA, COLUMN_COMMENT
|
|
446
|
+
FROM information_schema.COLUMNS
|
|
447
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
448
|
+
ORDER BY ORDINAL_POSITION`,
|
|
449
|
+
[body.databaseName, body.collectionName]
|
|
450
|
+
);
|
|
451
|
+
return colRows.map((cr) => {
|
|
452
|
+
let def = null;
|
|
453
|
+
const rawDef = cr.COLUMN_DEFAULT;
|
|
454
|
+
if (rawDef !== null && rawDef !== void 0) {
|
|
455
|
+
def = typeof rawDef === "string" ? rawDef : String(rawDef);
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
name: cr.COLUMN_NAME,
|
|
459
|
+
ordinal: Number(cr.ORDINAL_POSITION) || 0,
|
|
460
|
+
columnType: cr.COLUMN_TYPE,
|
|
461
|
+
dataType: cr.DATA_TYPE,
|
|
462
|
+
nullable: cr.IS_NULLABLE === "YES",
|
|
463
|
+
columnKey: cr.COLUMN_KEY || "",
|
|
464
|
+
defaultValue: def,
|
|
465
|
+
extra: cr.EXTRA || "",
|
|
466
|
+
comment: cr.COLUMN_COMMENT || ""
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
return {
|
|
471
|
+
success: true,
|
|
472
|
+
data: columns,
|
|
473
|
+
message: "\u83B7\u53D6\u8868\u7ED3\u6784\u6210\u529F"
|
|
474
|
+
};
|
|
475
|
+
} catch (error) {
|
|
476
|
+
return {
|
|
477
|
+
success: false,
|
|
478
|
+
message: error instanceof Error ? error.message : "\u83B7\u53D6\u8868\u7ED3\u6784\u5931\u8D25"
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
(0, import_f2e_server34.addRoute)(
|
|
484
|
+
"/api/collection/documents",
|
|
485
|
+
async (body) => {
|
|
486
|
+
const connections = appStorage.data.connections || [];
|
|
487
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
488
|
+
if (!connection) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
const page = body.page || 1;
|
|
495
|
+
const pageSize = body.pageSize || 20;
|
|
496
|
+
const skip = (page - 1) * pageSize;
|
|
497
|
+
try {
|
|
498
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
499
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
500
|
+
const table = escapeIdentifier(body.collectionName);
|
|
501
|
+
const result = await withMysqlConnection(connection, async (conn) => {
|
|
502
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
503
|
+
const pkCols = await getPrimaryKeyColumns(conn, body.databaseName, body.collectionName);
|
|
504
|
+
const [rows] = await conn.query(
|
|
505
|
+
`SELECT * FROM ${table} LIMIT ? OFFSET ?`,
|
|
506
|
+
[pageSize, skip]
|
|
507
|
+
);
|
|
508
|
+
const [countRows] = await conn.query(`SELECT COUNT(*) AS c FROM ${table}`);
|
|
509
|
+
const total = Number(countRows[0]?.c) || 0;
|
|
510
|
+
const documents = rows.map((r) => attachRowKeys({ ...r }, pkCols));
|
|
511
|
+
return {
|
|
512
|
+
documents,
|
|
513
|
+
total,
|
|
514
|
+
page,
|
|
515
|
+
pageSize,
|
|
516
|
+
hasPrimaryKey: pkCols.length > 0
|
|
517
|
+
};
|
|
518
|
+
});
|
|
519
|
+
return {
|
|
520
|
+
success: true,
|
|
521
|
+
data: result,
|
|
522
|
+
message: "\u83B7\u53D6\u6570\u636E\u884C\u6210\u529F"
|
|
523
|
+
};
|
|
524
|
+
} catch (error) {
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
message: error instanceof Error ? error.message : "\u83B7\u53D6\u6570\u636E\u884C\u5931\u8D25"
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
(0, import_f2e_server34.addRoute)(
|
|
533
|
+
"/api/collection/insert",
|
|
534
|
+
async (body) => {
|
|
535
|
+
const connections = appStorage.data.connections || [];
|
|
536
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
537
|
+
if (!connection) {
|
|
538
|
+
return {
|
|
539
|
+
success: false,
|
|
540
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
544
|
+
return {
|
|
545
|
+
success: false,
|
|
546
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
551
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
552
|
+
const doc = stripMetaFields(body.document);
|
|
553
|
+
const keys = Object.keys(doc);
|
|
554
|
+
if (keys.length === 0) {
|
|
555
|
+
throw new Error("\u63D2\u5165\u6570\u636E\u4E0D\u80FD\u4E3A\u7A7A");
|
|
556
|
+
}
|
|
557
|
+
const table = escapeIdentifier(body.collectionName);
|
|
558
|
+
const cols = keys.map((k) => escapeIdentifier(k)).join(", ");
|
|
559
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
560
|
+
const values = keys.map((k) => doc[k]);
|
|
561
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
562
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
563
|
+
await conn.query(`INSERT INTO ${table} (${cols}) VALUES (${placeholders})`, values);
|
|
564
|
+
});
|
|
565
|
+
return {
|
|
566
|
+
success: true,
|
|
567
|
+
message: "\u63D2\u5165\u6570\u636E\u6210\u529F"
|
|
568
|
+
};
|
|
569
|
+
} catch (error) {
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
message: error instanceof Error ? error.message : "\u63D2\u5165\u6570\u636E\u5931\u8D25"
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
(0, import_f2e_server34.addRoute)(
|
|
578
|
+
"/api/collection/update",
|
|
579
|
+
async (body) => {
|
|
580
|
+
const connections = appStorage.data.connections || [];
|
|
581
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
582
|
+
if (!connection) {
|
|
583
|
+
return {
|
|
584
|
+
success: false,
|
|
585
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
589
|
+
return {
|
|
590
|
+
success: false,
|
|
591
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
596
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
597
|
+
const table = escapeIdentifier(body.collectionName);
|
|
598
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
599
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
600
|
+
const pkCols = await getPrimaryKeyColumns(conn, body.databaseName, body.collectionName);
|
|
601
|
+
if (pkCols.length === 0) {
|
|
602
|
+
throw new Error("\u8BE5\u8868\u6CA1\u6709\u4E3B\u952E\uFF0C\u65E0\u6CD5\u5B89\u5168\u66F4\u65B0\u884C");
|
|
603
|
+
}
|
|
604
|
+
const pkFilter = body.filter.__pk && typeof body.filter.__pk === "object" ? body.filter.__pk : body.filter;
|
|
605
|
+
const update = stripMetaFields(body.update);
|
|
606
|
+
for (const k of pkCols) {
|
|
607
|
+
if (k in update) {
|
|
608
|
+
delete update[k];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const setKeys = Object.keys(update);
|
|
612
|
+
if (setKeys.length === 0) {
|
|
613
|
+
throw new Error("\u6CA1\u6709\u53EF\u66F4\u65B0\u7684\u5B57\u6BB5");
|
|
614
|
+
}
|
|
615
|
+
const setClause = setKeys.map((k) => `${escapeIdentifier(k)} = ?`).join(", ");
|
|
616
|
+
const setValues = setKeys.map((k) => update[k]);
|
|
617
|
+
const whereParts = pkCols.map((k) => `${escapeIdentifier(k)} = ?`);
|
|
618
|
+
const whereValues = pkCols.map((k) => {
|
|
619
|
+
const v = pkFilter[k];
|
|
620
|
+
if (v === void 0) {
|
|
621
|
+
throw new Error(`\u4E3B\u952E ${k} \u7F3A\u5931\uFF0C\u65E0\u6CD5\u5B9A\u4F4D\u884C`);
|
|
622
|
+
}
|
|
623
|
+
return v;
|
|
624
|
+
});
|
|
625
|
+
const [res] = await conn.query(`UPDATE ${table} SET ${setClause} WHERE ${whereParts.join(" AND ")}`, [
|
|
626
|
+
...setValues,
|
|
627
|
+
...whereValues
|
|
628
|
+
]);
|
|
629
|
+
const result = res;
|
|
630
|
+
return result.affectedRows ?? 0;
|
|
631
|
+
});
|
|
632
|
+
return {
|
|
633
|
+
success: true,
|
|
634
|
+
message: "\u66F4\u65B0\u6570\u636E\u6210\u529F"
|
|
635
|
+
};
|
|
636
|
+
} catch (error) {
|
|
637
|
+
return {
|
|
638
|
+
success: false,
|
|
639
|
+
message: error instanceof Error ? error.message : "\u66F4\u65B0\u6570\u636E\u5931\u8D25"
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
(0, import_f2e_server34.addRoute)(
|
|
645
|
+
"/api/collection/delete",
|
|
646
|
+
async (body) => {
|
|
647
|
+
const connections = appStorage.data.connections || [];
|
|
648
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
649
|
+
if (!connection) {
|
|
650
|
+
return {
|
|
651
|
+
success: false,
|
|
652
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
656
|
+
return {
|
|
657
|
+
success: false,
|
|
658
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
663
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
664
|
+
const table = escapeIdentifier(body.collectionName);
|
|
665
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
666
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
667
|
+
const pkCols = await getPrimaryKeyColumns(conn, body.databaseName, body.collectionName);
|
|
668
|
+
if (pkCols.length === 0) {
|
|
669
|
+
throw new Error("\u8BE5\u8868\u6CA1\u6709\u4E3B\u952E\uFF0C\u65E0\u6CD5\u5B89\u5168\u5220\u9664\u884C");
|
|
670
|
+
}
|
|
671
|
+
const pkFilter = body.filter.__pk && typeof body.filter.__pk === "object" ? body.filter.__pk : body.filter;
|
|
672
|
+
const whereParts = pkCols.map((k) => `${escapeIdentifier(k)} = ?`);
|
|
673
|
+
const whereValues = pkCols.map((k) => {
|
|
674
|
+
const v = pkFilter[k];
|
|
675
|
+
if (v === void 0) {
|
|
676
|
+
throw new Error(`\u4E3B\u952E ${k} \u7F3A\u5931\uFF0C\u65E0\u6CD5\u5B9A\u4F4D\u884C`);
|
|
677
|
+
}
|
|
678
|
+
return v;
|
|
679
|
+
});
|
|
680
|
+
await conn.query(`DELETE FROM ${table} WHERE ${whereParts.join(" AND ")}`, whereValues);
|
|
681
|
+
});
|
|
682
|
+
return {
|
|
683
|
+
success: true,
|
|
684
|
+
message: "\u5220\u9664\u6570\u636E\u6210\u529F"
|
|
685
|
+
};
|
|
686
|
+
} catch (error) {
|
|
687
|
+
return {
|
|
688
|
+
success: false,
|
|
689
|
+
message: error instanceof Error ? error.message : "\u5220\u9664\u6570\u636E\u5931\u8D25"
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
(0, import_f2e_server34.addRoute)("/api/collection/create", async (body) => {
|
|
695
|
+
const connections = appStorage.data.connections || [];
|
|
696
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
697
|
+
if (!connection) {
|
|
698
|
+
return {
|
|
699
|
+
success: false,
|
|
700
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
704
|
+
return {
|
|
705
|
+
success: false,
|
|
706
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
711
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
712
|
+
const table = escapeIdentifier(body.collectionName);
|
|
713
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
714
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
715
|
+
await conn.query(
|
|
716
|
+
`CREATE TABLE ${table} (
|
|
717
|
+
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
|
|
718
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
return {
|
|
722
|
+
success: true,
|
|
723
|
+
message: "\u521B\u5EFA\u6570\u636E\u8868\u6210\u529F"
|
|
724
|
+
};
|
|
725
|
+
} catch (error) {
|
|
726
|
+
return {
|
|
727
|
+
success: false,
|
|
728
|
+
message: error instanceof Error ? error.message : "\u521B\u5EFA\u6570\u636E\u8868\u5931\u8D25"
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
(0, import_f2e_server34.addRoute)("/api/collection/delete-collection", async (body) => {
|
|
733
|
+
const connections = appStorage.data.connections || [];
|
|
734
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
735
|
+
if (!connection) {
|
|
736
|
+
return {
|
|
737
|
+
success: false,
|
|
738
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
742
|
+
return {
|
|
743
|
+
success: false,
|
|
744
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
749
|
+
assertIdent2(body.collectionName, "\u6570\u636E\u8868");
|
|
750
|
+
const table = escapeIdentifier(body.collectionName);
|
|
751
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
752
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
753
|
+
await conn.query(`DROP TABLE ${table}`);
|
|
754
|
+
});
|
|
755
|
+
return {
|
|
756
|
+
success: true,
|
|
757
|
+
message: "\u5220\u9664\u6570\u636E\u8868\u6210\u529F"
|
|
758
|
+
};
|
|
759
|
+
} catch (error) {
|
|
760
|
+
return {
|
|
761
|
+
success: false,
|
|
762
|
+
message: error instanceof Error ? error.message : "\u5220\u9664\u6570\u636E\u8868\u5931\u8D25"
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
(0, import_f2e_server34.addRoute)(
|
|
767
|
+
"/api/collection/rename",
|
|
768
|
+
async (body) => {
|
|
769
|
+
const connections = appStorage.data.connections || [];
|
|
770
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
771
|
+
if (!connection) {
|
|
772
|
+
return {
|
|
773
|
+
success: false,
|
|
774
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
if (isReadOnlyDatabase(body.databaseName)) {
|
|
778
|
+
return {
|
|
779
|
+
success: false,
|
|
780
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
assertIdent2(body.databaseName, "\u6570\u636E\u5E93");
|
|
785
|
+
assertIdent2(body.oldCollectionName, "\u6570\u636E\u8868");
|
|
786
|
+
assertIdent2(body.newCollectionName, "\u6570\u636E\u8868");
|
|
787
|
+
const oldT = escapeIdentifier(body.oldCollectionName);
|
|
788
|
+
const newT = escapeIdentifier(body.newCollectionName);
|
|
789
|
+
await withMysqlConnection(connection, async (conn) => {
|
|
790
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
791
|
+
await conn.query(`RENAME TABLE ${oldT} TO ${newT}`);
|
|
792
|
+
});
|
|
793
|
+
return {
|
|
794
|
+
success: true,
|
|
795
|
+
message: "\u91CD\u547D\u540D\u6570\u636E\u8868\u6210\u529F"
|
|
796
|
+
};
|
|
797
|
+
} catch (error) {
|
|
798
|
+
return {
|
|
799
|
+
success: false,
|
|
800
|
+
message: error instanceof Error ? error.message : "\u91CD\u547D\u540D\u6570\u636E\u8868\u5931\u8D25"
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
// server/api/query.ts
|
|
807
|
+
var import_f2e_server35 = require("f2e-server3");
|
|
808
|
+
function assertIdent3(name, label) {
|
|
809
|
+
if (!/^[a-zA-Z0-9_]+$/.test(name)) {
|
|
810
|
+
throw new Error(`${label}\u540D\u79F0\u5305\u542B\u975E\u6CD5\u5B57\u7B26`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function parseJsonObject(query) {
|
|
814
|
+
const trimmed = query.trim();
|
|
815
|
+
if (!trimmed) {
|
|
816
|
+
return {};
|
|
817
|
+
}
|
|
818
|
+
const obj = JSON.parse(trimmed);
|
|
819
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
820
|
+
throw new Error('\u6761\u4EF6\u5FC5\u987B\u662F JSON \u5BF9\u8C61\uFF0C\u4F8B\u5982 {"id":1}');
|
|
821
|
+
}
|
|
822
|
+
return obj;
|
|
823
|
+
}
|
|
824
|
+
function buildWhereClause(obj) {
|
|
825
|
+
const keys = Object.keys(obj).filter((k) => /^[a-zA-Z0-9_]+$/.test(k));
|
|
826
|
+
if (keys.length === 0) {
|
|
827
|
+
return { sql: "1=1", values: [] };
|
|
828
|
+
}
|
|
829
|
+
const parts = keys.map((k) => `${escapeIdentifier(k)} = ?`);
|
|
830
|
+
const values = keys.map((k) => obj[k]);
|
|
831
|
+
return { sql: parts.join(" AND "), values };
|
|
832
|
+
}
|
|
833
|
+
function assertSafeSelect(sql) {
|
|
834
|
+
const s = sql.trim();
|
|
835
|
+
if (!/^\s*select\b/i.test(s)) {
|
|
836
|
+
throw new Error("\u4EC5\u5141\u8BB8\u4EE5 SELECT \u5F00\u5934\u7684\u67E5\u8BE2");
|
|
837
|
+
}
|
|
838
|
+
if (/\b(into|outfile|dumpfile|for\s+update)\b/i.test(s)) {
|
|
839
|
+
throw new Error("\u8BE5\u67E5\u8BE2\u5305\u542B\u4E0D\u5141\u8BB8\u7684\u5173\u952E\u5B57");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
(0, import_f2e_server35.addRoute)(
|
|
843
|
+
"/api/query/execute",
|
|
844
|
+
async (body) => {
|
|
845
|
+
const connections = appStorage.data.connections || [];
|
|
846
|
+
const connection = connections.find((c) => c._id === body.connectionId);
|
|
847
|
+
if (!connection) {
|
|
848
|
+
return {
|
|
849
|
+
success: false,
|
|
850
|
+
message: "\u8FDE\u63A5\u4E0D\u5B58\u5728"
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
const operation = body.operation || "find";
|
|
854
|
+
if (operation === "insertOne" && isReadOnlyDatabase(body.databaseName)) {
|
|
855
|
+
return {
|
|
856
|
+
success: false,
|
|
857
|
+
message: "\u7CFB\u7EDF\u5E93\u4E0D\u5141\u8BB8\u5199\u5165"
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
assertIdent3(body.databaseName, "\u6570\u636E\u5E93");
|
|
861
|
+
assertIdent3(body.collectionName, "\u6570\u636E\u8868");
|
|
862
|
+
const table = escapeIdentifier(body.collectionName);
|
|
863
|
+
try {
|
|
864
|
+
const result = await withMysqlConnection(connection, async (conn) => {
|
|
865
|
+
await conn.query(`USE ${escapeIdentifier(body.databaseName)}`);
|
|
866
|
+
switch (operation) {
|
|
867
|
+
case "find": {
|
|
868
|
+
const obj = parseJsonObject(body.query);
|
|
869
|
+
const { sql: where, values } = buildWhereClause(obj);
|
|
870
|
+
const sql = `SELECT * FROM ${table} WHERE ${where} LIMIT 100`;
|
|
871
|
+
const [rows] = await conn.query(sql, values);
|
|
872
|
+
return rows;
|
|
873
|
+
}
|
|
874
|
+
case "findOne": {
|
|
875
|
+
const obj = parseJsonObject(body.query);
|
|
876
|
+
const { sql: where, values } = buildWhereClause(obj);
|
|
877
|
+
const sql = `SELECT * FROM ${table} WHERE ${where} LIMIT 1`;
|
|
878
|
+
const [rows] = await conn.query(sql, values);
|
|
879
|
+
return rows[0] ?? null;
|
|
880
|
+
}
|
|
881
|
+
case "count": {
|
|
882
|
+
const obj = parseJsonObject(body.query);
|
|
883
|
+
const { sql: where, values } = buildWhereClause(obj);
|
|
884
|
+
const sql = `SELECT COUNT(*) AS c FROM ${table} WHERE ${where}`;
|
|
885
|
+
const [rows] = await conn.query(sql, values);
|
|
886
|
+
return Number(rows[0]?.c) || 0;
|
|
887
|
+
}
|
|
888
|
+
case "aggregate": {
|
|
889
|
+
const sql = body.query.trim();
|
|
890
|
+
assertSafeSelect(sql);
|
|
891
|
+
const [rows] = await conn.query(sql);
|
|
892
|
+
return rows;
|
|
893
|
+
}
|
|
894
|
+
case "insertOne": {
|
|
895
|
+
let documentObj = {};
|
|
896
|
+
if (body.document?.trim()) {
|
|
897
|
+
documentObj = parseJsonObject(body.document);
|
|
898
|
+
} else {
|
|
899
|
+
throw new Error("\u63D2\u5165\u64CD\u4F5C\u9700\u8981\u63D0\u4F9B\u6587\u6863 JSON");
|
|
900
|
+
}
|
|
901
|
+
const keys = Object.keys(documentObj).filter((k) => /^[a-zA-Z0-9_]+$/.test(k));
|
|
902
|
+
if (keys.length === 0) {
|
|
903
|
+
throw new Error("\u63D2\u5165\u5B57\u6BB5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
904
|
+
}
|
|
905
|
+
const cols = keys.map((k) => escapeIdentifier(k)).join(", ");
|
|
906
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
907
|
+
const values = keys.map((k) => documentObj[k]);
|
|
908
|
+
await conn.query(`INSERT INTO ${table} (${cols}) VALUES (${placeholders})`, values);
|
|
909
|
+
return { ok: true };
|
|
910
|
+
}
|
|
911
|
+
default:
|
|
912
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u64CD\u4F5C\u7C7B\u578B: ${operation}`);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
return {
|
|
916
|
+
success: true,
|
|
917
|
+
data: result,
|
|
918
|
+
message: operation === "insertOne" ? "\u63D2\u5165\u6210\u529F" : "\u67E5\u8BE2\u6267\u884C\u6210\u529F"
|
|
919
|
+
};
|
|
920
|
+
} catch (error) {
|
|
921
|
+
return {
|
|
922
|
+
success: false,
|
|
923
|
+
message: error instanceof Error ? error.message : "\u67E5\u8BE2\u6267\u884C\u5931\u8D25"
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
);
|