datapeek 0.1.6 → 0.1.8
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/dist/cli/chunk-5X2YZYYM.js +137 -0
- package/dist/cli/dev.js +104 -17
- package/dist/cli/index.js +104 -17
- package/dist/cli/mssql-D7NDIUG7.js +20 -0
- package/dist/client/assets/index-C32MC3if.js +239 -0
- package/dist/client/assets/index-piPKidzx.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chunk-XFS5KTMI.js +134 -0
- package/dist/server/dev.js +104 -17
- package/dist/server/index.js +104 -17
- package/dist/server/mssql-UUT2IKOP.js +18 -0
- package/package.json +1 -1
- package/dist/client/assets/index-DPwYsgsi.css +0 -1
- package/dist/client/assets/index-Dd2TX-fW.js +0 -192
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/server/db/mssql.ts
|
|
5
|
+
import sql from "mssql";
|
|
6
|
+
var pool = null;
|
|
7
|
+
function parseConnectionString(connectionString) {
|
|
8
|
+
const config = {
|
|
9
|
+
server: "",
|
|
10
|
+
database: "",
|
|
11
|
+
options: {
|
|
12
|
+
encrypt: true,
|
|
13
|
+
trustServerCertificate: false,
|
|
14
|
+
enableArithAbort: true
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const parts = connectionString.split(";");
|
|
18
|
+
for (const part of parts) {
|
|
19
|
+
const [key, ...valueParts] = part.split("=");
|
|
20
|
+
const value = valueParts.join("=").trim();
|
|
21
|
+
const keyLower = key.trim().toLowerCase();
|
|
22
|
+
switch (keyLower) {
|
|
23
|
+
case "server":
|
|
24
|
+
case "data source":
|
|
25
|
+
let serverValue = value;
|
|
26
|
+
if (serverValue.startsWith("tcp:")) {
|
|
27
|
+
serverValue = serverValue.substring(4);
|
|
28
|
+
}
|
|
29
|
+
const [serverHost, serverPort] = serverValue.split(",");
|
|
30
|
+
config.server = serverHost.trim();
|
|
31
|
+
if (serverPort) {
|
|
32
|
+
config.port = parseInt(serverPort.trim(), 10);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
case "database":
|
|
36
|
+
case "initial catalog":
|
|
37
|
+
config.database = value;
|
|
38
|
+
break;
|
|
39
|
+
case "user id":
|
|
40
|
+
case "userid":
|
|
41
|
+
case "uid":
|
|
42
|
+
config.user = value;
|
|
43
|
+
break;
|
|
44
|
+
case "password":
|
|
45
|
+
case "pwd":
|
|
46
|
+
config.password = value;
|
|
47
|
+
break;
|
|
48
|
+
case "port":
|
|
49
|
+
config.port = parseInt(value, 10);
|
|
50
|
+
break;
|
|
51
|
+
case "encrypt":
|
|
52
|
+
config.options.encrypt = value.toLowerCase() === "true";
|
|
53
|
+
break;
|
|
54
|
+
case "trustservercertificate":
|
|
55
|
+
case "trust server certificate":
|
|
56
|
+
config.options.trustServerCertificate = value.toLowerCase() === "true";
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
async function testConnection(config) {
|
|
63
|
+
const testPool = typeof config === "string" ? new sql.ConnectionPool(parseConnectionString(config)) : new sql.ConnectionPool(config);
|
|
64
|
+
try {
|
|
65
|
+
await testPool.connect();
|
|
66
|
+
await testPool.close();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function connect(config) {
|
|
72
|
+
await disconnect();
|
|
73
|
+
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
74
|
+
pool = new sql.ConnectionPool(connectionConfig);
|
|
75
|
+
try {
|
|
76
|
+
await pool.connect();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
pool = null;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function disconnect() {
|
|
83
|
+
if (pool) {
|
|
84
|
+
try {
|
|
85
|
+
await pool.close();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
}
|
|
88
|
+
pool = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function getConnection() {
|
|
92
|
+
return pool;
|
|
93
|
+
}
|
|
94
|
+
async function executeQuery(query, parameters) {
|
|
95
|
+
if (!pool || !pool.connected) {
|
|
96
|
+
throw new Error("Not connected to database");
|
|
97
|
+
}
|
|
98
|
+
const request = pool.request();
|
|
99
|
+
if (parameters) {
|
|
100
|
+
for (const param of parameters) {
|
|
101
|
+
if (param.type) {
|
|
102
|
+
request.input(param.name, param.type, param.value);
|
|
103
|
+
} else {
|
|
104
|
+
request.input(param.name, param.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const result = await request.query(query);
|
|
109
|
+
return result.recordset || [];
|
|
110
|
+
}
|
|
111
|
+
async function executeQueryMultiple(query, parameters) {
|
|
112
|
+
if (!pool || !pool.connected) {
|
|
113
|
+
throw new Error("Not connected to database");
|
|
114
|
+
}
|
|
115
|
+
const request = pool.request();
|
|
116
|
+
if (parameters) {
|
|
117
|
+
for (const param of parameters) {
|
|
118
|
+
if (param.type) {
|
|
119
|
+
request.input(param.name, param.type, param.value);
|
|
120
|
+
} else {
|
|
121
|
+
request.input(param.name, param.value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const result = await request.query(query);
|
|
126
|
+
return result.recordsets || [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
parseConnectionString,
|
|
131
|
+
testConnection,
|
|
132
|
+
connect,
|
|
133
|
+
disconnect,
|
|
134
|
+
getConnection,
|
|
135
|
+
executeQuery,
|
|
136
|
+
executeQueryMultiple
|
|
137
|
+
};
|
package/dist/cli/dev.js
CHANGED
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
connect,
|
|
5
5
|
disconnect,
|
|
6
6
|
executeQuery,
|
|
7
|
+
executeQueryMultiple,
|
|
7
8
|
getConnection,
|
|
8
9
|
testConnection
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5X2YZYYM.js";
|
|
10
11
|
|
|
11
12
|
// src/server/index.ts
|
|
12
13
|
import express from "express";
|
|
@@ -87,15 +88,20 @@ connectionRoutes.delete("/", async (req, res) => {
|
|
|
87
88
|
});
|
|
88
89
|
connectionRoutes.get("/status", async (req, res) => {
|
|
89
90
|
try {
|
|
90
|
-
const { getConnection: getConnection2, executeQuery:
|
|
91
|
+
const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-D7NDIUG7.js");
|
|
91
92
|
const pool = getConnection2();
|
|
92
93
|
if (pool && pool.connected) {
|
|
93
94
|
try {
|
|
94
|
-
const result = await
|
|
95
|
+
const result = await executeQuery3("SELECT DB_NAME() as databaseName");
|
|
95
96
|
const databaseName = result[0]?.databaseName || null;
|
|
96
97
|
res.json({ connected: true, databaseName });
|
|
97
|
-
} catch {
|
|
98
|
-
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const errorMessage = error.message || "";
|
|
100
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
101
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
102
|
+
await disconnect2();
|
|
103
|
+
}
|
|
104
|
+
res.json({ connected: false });
|
|
99
105
|
}
|
|
100
106
|
} else {
|
|
101
107
|
res.json({ connected: false });
|
|
@@ -126,6 +132,11 @@ tableRoutes.get("/", async (req, res) => {
|
|
|
126
132
|
const result = await executeQuery(query);
|
|
127
133
|
res.json(result);
|
|
128
134
|
} catch (error) {
|
|
135
|
+
const errorMessage = error.message || "";
|
|
136
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
137
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
138
|
+
await disconnect2();
|
|
139
|
+
}
|
|
129
140
|
res.status(500).json({ error: error.message || "Failed to fetch tables" });
|
|
130
141
|
}
|
|
131
142
|
});
|
|
@@ -164,6 +175,11 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
164
175
|
]);
|
|
165
176
|
res.json(result);
|
|
166
177
|
} catch (error) {
|
|
178
|
+
const errorMessage = error.message || "";
|
|
179
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
180
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
181
|
+
await disconnect2();
|
|
182
|
+
}
|
|
167
183
|
res.status(500).json({ error: error.message || "Failed to fetch table structure" });
|
|
168
184
|
}
|
|
169
185
|
});
|
|
@@ -175,12 +191,63 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
175
191
|
const sortColumn = req.query.sortColumn;
|
|
176
192
|
const sortDirection = req.query.sortDirection || "asc";
|
|
177
193
|
const offset = (page - 1) * pageSize;
|
|
194
|
+
const filters = {};
|
|
195
|
+
Object.keys(req.query).forEach((key) => {
|
|
196
|
+
const match = key.match(/^filter\[(.+)\]$/);
|
|
197
|
+
if (match && req.query[key] && String(req.query[key]).trim()) {
|
|
198
|
+
filters[match[1]] = String(req.query[key]).trim();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
console.log("Received filters from query:", filters);
|
|
202
|
+
console.log("All query params:", req.query);
|
|
178
203
|
const pool = getConnection();
|
|
179
204
|
if (!pool || !pool.connected) {
|
|
180
205
|
return res.status(400).json({ error: "Not connected to database" });
|
|
181
206
|
}
|
|
182
|
-
const
|
|
183
|
-
|
|
207
|
+
const filterColumns = [];
|
|
208
|
+
if (Object.keys(filters).length > 0) {
|
|
209
|
+
try {
|
|
210
|
+
const columnNames = Object.keys(filters);
|
|
211
|
+
const placeholders = columnNames.map((_, i) => `@col${i}`).join(", ");
|
|
212
|
+
const validateQuery = `
|
|
213
|
+
SELECT COLUMN_NAME
|
|
214
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
215
|
+
WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND COLUMN_NAME IN (${placeholders})
|
|
216
|
+
`;
|
|
217
|
+
const validateParams = [
|
|
218
|
+
{ name: "schema", value: schema, type: sql.NVarChar },
|
|
219
|
+
{ name: "table", value: table, type: sql.NVarChar },
|
|
220
|
+
...columnNames.map((col, i) => ({ name: `col${i}`, value: col, type: sql.NVarChar }))
|
|
221
|
+
];
|
|
222
|
+
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
223
|
+
filterColumns.push(...validateResult.map((r) => r.COLUMN_NAME));
|
|
224
|
+
console.log("Validated filter columns:", filterColumns, "from filters:", filters);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.error("Error validating filter columns:", e);
|
|
227
|
+
console.error("Filters that failed validation:", filters);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
let whereClause = "";
|
|
231
|
+
const filterParams = [];
|
|
232
|
+
if (filterColumns.length > 0) {
|
|
233
|
+
const validFilters = filterColumns.filter((col) => {
|
|
234
|
+
const filterValue = filters[col];
|
|
235
|
+
return filterValue && filterValue.trim() !== "";
|
|
236
|
+
});
|
|
237
|
+
if (validFilters.length > 0) {
|
|
238
|
+
const whereConditions = validFilters.map((col, index) => {
|
|
239
|
+
const filterValue = filters[col];
|
|
240
|
+
filterParams.push({ name: `filter${index}`, value: `%${filterValue.trim()}%`, type: sql.NVarChar });
|
|
241
|
+
return `[${col}] LIKE @filter${index}`;
|
|
242
|
+
});
|
|
243
|
+
whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
|
244
|
+
console.log("Applying WHERE clause:", whereClause);
|
|
245
|
+
console.log("Filter params:", filterParams);
|
|
246
|
+
console.log("Valid filters:", validFilters);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const countQuery = `SELECT COUNT(*) as total FROM [${schema}].[${table}]${whereClause ? " " + whereClause : ""}`;
|
|
250
|
+
const countResult = await executeQuery(countQuery, filterParams.length > 0 ? filterParams : []);
|
|
184
251
|
const total = countResult[0]?.total || 0;
|
|
185
252
|
let orderByColumn = sortColumn || "";
|
|
186
253
|
let orderByDirection = sortDirection?.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
@@ -224,24 +291,26 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
224
291
|
let data;
|
|
225
292
|
let generatedQuery = "";
|
|
226
293
|
if (orderByColumn) {
|
|
227
|
-
generatedQuery = `SELECT * FROM [${schema}].[${table}]
|
|
294
|
+
generatedQuery = `SELECT * FROM [${schema}].[${table}]${whereClause ? "\n" + whereClause : ""}
|
|
228
295
|
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
229
296
|
OFFSET ${offset} ROWS
|
|
230
297
|
FETCH NEXT ${pageSize} ROWS ONLY`;
|
|
231
298
|
const dataQuery = `
|
|
232
299
|
SELECT * FROM [${schema}].[${table}]
|
|
300
|
+
${whereClause}
|
|
233
301
|
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
234
302
|
OFFSET @offset ROWS
|
|
235
303
|
FETCH NEXT @pageSize ROWS ONLY
|
|
236
304
|
`;
|
|
237
305
|
data = await executeQuery(dataQuery, [
|
|
238
306
|
{ name: "offset", value: offset, type: sql.Int },
|
|
239
|
-
{ name: "pageSize", value: pageSize, type: sql.Int }
|
|
307
|
+
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
308
|
+
...filterParams
|
|
240
309
|
]);
|
|
241
310
|
} else {
|
|
242
311
|
generatedQuery = `SELECT * FROM (
|
|
243
312
|
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
244
|
-
FROM [${schema}].[${table}]
|
|
313
|
+
FROM [${schema}].[${table}]${whereClause ? "\n " + whereClause : ""}
|
|
245
314
|
) t
|
|
246
315
|
WHERE rn > ${offset} AND rn <= ${offset + pageSize}
|
|
247
316
|
ORDER BY rn`;
|
|
@@ -249,13 +318,15 @@ ORDER BY rn`;
|
|
|
249
318
|
SELECT * FROM (
|
|
250
319
|
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
251
320
|
FROM [${schema}].[${table}]
|
|
321
|
+
${whereClause}
|
|
252
322
|
) t
|
|
253
323
|
WHERE rn > @offset AND rn <= @offset + @pageSize
|
|
254
324
|
ORDER BY rn
|
|
255
325
|
`;
|
|
256
326
|
data = await executeQuery(dataQuery, [
|
|
257
327
|
{ name: "offset", value: offset, type: sql.Int },
|
|
258
|
-
{ name: "pageSize", value: pageSize, type: sql.Int }
|
|
328
|
+
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
329
|
+
...filterParams
|
|
259
330
|
]);
|
|
260
331
|
data = data.map((row) => {
|
|
261
332
|
const { rn, ...rest } = row;
|
|
@@ -274,7 +345,11 @@ ORDER BY rn`;
|
|
|
274
345
|
});
|
|
275
346
|
} catch (error) {
|
|
276
347
|
console.error("Error fetching table data:", error);
|
|
277
|
-
const errorMessage = error.message || "
|
|
348
|
+
const errorMessage = error.message || "";
|
|
349
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
350
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
351
|
+
await disconnect2();
|
|
352
|
+
}
|
|
278
353
|
const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
|
|
279
354
|
res.status(500).json({
|
|
280
355
|
error: errorMessage,
|
|
@@ -299,17 +374,29 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
299
374
|
});
|
|
300
375
|
}
|
|
301
376
|
const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
|
|
302
|
-
const hasDangerousKeyword = dangerousKeywords.some(
|
|
303
|
-
|
|
304
|
-
|
|
377
|
+
const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
|
|
378
|
+
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
379
|
+
return regex.test(sqlQuery);
|
|
380
|
+
});
|
|
305
381
|
if (hasDangerousKeyword) {
|
|
306
382
|
return res.status(400).json({
|
|
307
383
|
error: "Query contains prohibited keywords. Only SELECT queries are allowed."
|
|
308
384
|
});
|
|
309
385
|
}
|
|
310
|
-
const
|
|
311
|
-
|
|
386
|
+
const startTime = Date.now();
|
|
387
|
+
const resultSets = await executeQueryMultiple(sqlQuery);
|
|
388
|
+
const executionTime = Date.now() - startTime;
|
|
389
|
+
res.json({
|
|
390
|
+
data: resultSets[0] || [],
|
|
391
|
+
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
392
|
+
executionTime
|
|
393
|
+
});
|
|
312
394
|
} catch (error) {
|
|
395
|
+
const errorMessage = error.message || "";
|
|
396
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
397
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
398
|
+
await disconnect2();
|
|
399
|
+
}
|
|
313
400
|
res.status(500).json({
|
|
314
401
|
error: error.message || "Query execution failed",
|
|
315
402
|
details: error.originalError?.message
|
package/dist/cli/index.js
CHANGED
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
connect,
|
|
5
5
|
disconnect,
|
|
6
6
|
executeQuery,
|
|
7
|
+
executeQueryMultiple,
|
|
7
8
|
getConnection,
|
|
8
9
|
testConnection
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5X2YZYYM.js";
|
|
10
11
|
|
|
11
12
|
// src/cli/index.ts
|
|
12
13
|
import { Command } from "commander";
|
|
@@ -90,15 +91,20 @@ connectionRoutes.delete("/", async (req, res) => {
|
|
|
90
91
|
});
|
|
91
92
|
connectionRoutes.get("/status", async (req, res) => {
|
|
92
93
|
try {
|
|
93
|
-
const { getConnection: getConnection2, executeQuery:
|
|
94
|
+
const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-D7NDIUG7.js");
|
|
94
95
|
const pool = getConnection2();
|
|
95
96
|
if (pool && pool.connected) {
|
|
96
97
|
try {
|
|
97
|
-
const result = await
|
|
98
|
+
const result = await executeQuery3("SELECT DB_NAME() as databaseName");
|
|
98
99
|
const databaseName = result[0]?.databaseName || null;
|
|
99
100
|
res.json({ connected: true, databaseName });
|
|
100
|
-
} catch {
|
|
101
|
-
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const errorMessage = error.message || "";
|
|
103
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
104
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
105
|
+
await disconnect2();
|
|
106
|
+
}
|
|
107
|
+
res.json({ connected: false });
|
|
102
108
|
}
|
|
103
109
|
} else {
|
|
104
110
|
res.json({ connected: false });
|
|
@@ -129,6 +135,11 @@ tableRoutes.get("/", async (req, res) => {
|
|
|
129
135
|
const result = await executeQuery(query);
|
|
130
136
|
res.json(result);
|
|
131
137
|
} catch (error) {
|
|
138
|
+
const errorMessage = error.message || "";
|
|
139
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
140
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
141
|
+
await disconnect2();
|
|
142
|
+
}
|
|
132
143
|
res.status(500).json({ error: error.message || "Failed to fetch tables" });
|
|
133
144
|
}
|
|
134
145
|
});
|
|
@@ -167,6 +178,11 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
167
178
|
]);
|
|
168
179
|
res.json(result);
|
|
169
180
|
} catch (error) {
|
|
181
|
+
const errorMessage = error.message || "";
|
|
182
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
183
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
184
|
+
await disconnect2();
|
|
185
|
+
}
|
|
170
186
|
res.status(500).json({ error: error.message || "Failed to fetch table structure" });
|
|
171
187
|
}
|
|
172
188
|
});
|
|
@@ -178,12 +194,63 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
178
194
|
const sortColumn = req.query.sortColumn;
|
|
179
195
|
const sortDirection = req.query.sortDirection || "asc";
|
|
180
196
|
const offset = (page - 1) * pageSize;
|
|
197
|
+
const filters = {};
|
|
198
|
+
Object.keys(req.query).forEach((key) => {
|
|
199
|
+
const match = key.match(/^filter\[(.+)\]$/);
|
|
200
|
+
if (match && req.query[key] && String(req.query[key]).trim()) {
|
|
201
|
+
filters[match[1]] = String(req.query[key]).trim();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
console.log("Received filters from query:", filters);
|
|
205
|
+
console.log("All query params:", req.query);
|
|
181
206
|
const pool = getConnection();
|
|
182
207
|
if (!pool || !pool.connected) {
|
|
183
208
|
return res.status(400).json({ error: "Not connected to database" });
|
|
184
209
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
210
|
+
const filterColumns = [];
|
|
211
|
+
if (Object.keys(filters).length > 0) {
|
|
212
|
+
try {
|
|
213
|
+
const columnNames = Object.keys(filters);
|
|
214
|
+
const placeholders = columnNames.map((_, i) => `@col${i}`).join(", ");
|
|
215
|
+
const validateQuery = `
|
|
216
|
+
SELECT COLUMN_NAME
|
|
217
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
218
|
+
WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND COLUMN_NAME IN (${placeholders})
|
|
219
|
+
`;
|
|
220
|
+
const validateParams = [
|
|
221
|
+
{ name: "schema", value: schema, type: sql.NVarChar },
|
|
222
|
+
{ name: "table", value: table, type: sql.NVarChar },
|
|
223
|
+
...columnNames.map((col, i) => ({ name: `col${i}`, value: col, type: sql.NVarChar }))
|
|
224
|
+
];
|
|
225
|
+
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
226
|
+
filterColumns.push(...validateResult.map((r) => r.COLUMN_NAME));
|
|
227
|
+
console.log("Validated filter columns:", filterColumns, "from filters:", filters);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.error("Error validating filter columns:", e);
|
|
230
|
+
console.error("Filters that failed validation:", filters);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
let whereClause = "";
|
|
234
|
+
const filterParams = [];
|
|
235
|
+
if (filterColumns.length > 0) {
|
|
236
|
+
const validFilters = filterColumns.filter((col) => {
|
|
237
|
+
const filterValue = filters[col];
|
|
238
|
+
return filterValue && filterValue.trim() !== "";
|
|
239
|
+
});
|
|
240
|
+
if (validFilters.length > 0) {
|
|
241
|
+
const whereConditions = validFilters.map((col, index) => {
|
|
242
|
+
const filterValue = filters[col];
|
|
243
|
+
filterParams.push({ name: `filter${index}`, value: `%${filterValue.trim()}%`, type: sql.NVarChar });
|
|
244
|
+
return `[${col}] LIKE @filter${index}`;
|
|
245
|
+
});
|
|
246
|
+
whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
|
247
|
+
console.log("Applying WHERE clause:", whereClause);
|
|
248
|
+
console.log("Filter params:", filterParams);
|
|
249
|
+
console.log("Valid filters:", validFilters);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const countQuery = `SELECT COUNT(*) as total FROM [${schema}].[${table}]${whereClause ? " " + whereClause : ""}`;
|
|
253
|
+
const countResult = await executeQuery(countQuery, filterParams.length > 0 ? filterParams : []);
|
|
187
254
|
const total = countResult[0]?.total || 0;
|
|
188
255
|
let orderByColumn = sortColumn || "";
|
|
189
256
|
let orderByDirection = sortDirection?.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
@@ -227,24 +294,26 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
227
294
|
let data;
|
|
228
295
|
let generatedQuery = "";
|
|
229
296
|
if (orderByColumn) {
|
|
230
|
-
generatedQuery = `SELECT * FROM [${schema}].[${table}]
|
|
297
|
+
generatedQuery = `SELECT * FROM [${schema}].[${table}]${whereClause ? "\n" + whereClause : ""}
|
|
231
298
|
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
232
299
|
OFFSET ${offset} ROWS
|
|
233
300
|
FETCH NEXT ${pageSize} ROWS ONLY`;
|
|
234
301
|
const dataQuery = `
|
|
235
302
|
SELECT * FROM [${schema}].[${table}]
|
|
303
|
+
${whereClause}
|
|
236
304
|
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
237
305
|
OFFSET @offset ROWS
|
|
238
306
|
FETCH NEXT @pageSize ROWS ONLY
|
|
239
307
|
`;
|
|
240
308
|
data = await executeQuery(dataQuery, [
|
|
241
309
|
{ name: "offset", value: offset, type: sql.Int },
|
|
242
|
-
{ name: "pageSize", value: pageSize, type: sql.Int }
|
|
310
|
+
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
311
|
+
...filterParams
|
|
243
312
|
]);
|
|
244
313
|
} else {
|
|
245
314
|
generatedQuery = `SELECT * FROM (
|
|
246
315
|
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
247
|
-
FROM [${schema}].[${table}]
|
|
316
|
+
FROM [${schema}].[${table}]${whereClause ? "\n " + whereClause : ""}
|
|
248
317
|
) t
|
|
249
318
|
WHERE rn > ${offset} AND rn <= ${offset + pageSize}
|
|
250
319
|
ORDER BY rn`;
|
|
@@ -252,13 +321,15 @@ ORDER BY rn`;
|
|
|
252
321
|
SELECT * FROM (
|
|
253
322
|
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
254
323
|
FROM [${schema}].[${table}]
|
|
324
|
+
${whereClause}
|
|
255
325
|
) t
|
|
256
326
|
WHERE rn > @offset AND rn <= @offset + @pageSize
|
|
257
327
|
ORDER BY rn
|
|
258
328
|
`;
|
|
259
329
|
data = await executeQuery(dataQuery, [
|
|
260
330
|
{ name: "offset", value: offset, type: sql.Int },
|
|
261
|
-
{ name: "pageSize", value: pageSize, type: sql.Int }
|
|
331
|
+
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
332
|
+
...filterParams
|
|
262
333
|
]);
|
|
263
334
|
data = data.map((row) => {
|
|
264
335
|
const { rn, ...rest } = row;
|
|
@@ -277,7 +348,11 @@ ORDER BY rn`;
|
|
|
277
348
|
});
|
|
278
349
|
} catch (error) {
|
|
279
350
|
console.error("Error fetching table data:", error);
|
|
280
|
-
const errorMessage = error.message || "
|
|
351
|
+
const errorMessage = error.message || "";
|
|
352
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
353
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
354
|
+
await disconnect2();
|
|
355
|
+
}
|
|
281
356
|
const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
|
|
282
357
|
res.status(500).json({
|
|
283
358
|
error: errorMessage,
|
|
@@ -302,17 +377,29 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
302
377
|
});
|
|
303
378
|
}
|
|
304
379
|
const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
|
|
305
|
-
const hasDangerousKeyword = dangerousKeywords.some(
|
|
306
|
-
|
|
307
|
-
|
|
380
|
+
const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
|
|
381
|
+
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
382
|
+
return regex.test(sqlQuery);
|
|
383
|
+
});
|
|
308
384
|
if (hasDangerousKeyword) {
|
|
309
385
|
return res.status(400).json({
|
|
310
386
|
error: "Query contains prohibited keywords. Only SELECT queries are allowed."
|
|
311
387
|
});
|
|
312
388
|
}
|
|
313
|
-
const
|
|
314
|
-
|
|
389
|
+
const startTime = Date.now();
|
|
390
|
+
const resultSets = await executeQueryMultiple(sqlQuery);
|
|
391
|
+
const executionTime = Date.now() - startTime;
|
|
392
|
+
res.json({
|
|
393
|
+
data: resultSets[0] || [],
|
|
394
|
+
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
395
|
+
executionTime
|
|
396
|
+
});
|
|
315
397
|
} catch (error) {
|
|
398
|
+
const errorMessage = error.message || "";
|
|
399
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
400
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
401
|
+
await disconnect2();
|
|
402
|
+
}
|
|
316
403
|
res.status(500).json({
|
|
317
404
|
error: error.message || "Query execution failed",
|
|
318
405
|
details: error.originalError?.message
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
connect,
|
|
5
|
+
disconnect,
|
|
6
|
+
executeQuery,
|
|
7
|
+
executeQueryMultiple,
|
|
8
|
+
getConnection,
|
|
9
|
+
parseConnectionString,
|
|
10
|
+
testConnection
|
|
11
|
+
} from "./chunk-5X2YZYYM.js";
|
|
12
|
+
export {
|
|
13
|
+
connect,
|
|
14
|
+
disconnect,
|
|
15
|
+
executeQuery,
|
|
16
|
+
executeQueryMultiple,
|
|
17
|
+
getConnection,
|
|
18
|
+
parseConnectionString,
|
|
19
|
+
testConnection
|
|
20
|
+
};
|