flex-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/api/auth.d.ts +6 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +111 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/configs.d.ts +6 -0
- package/dist/api/configs.d.ts.map +1 -0
- package/dist/api/configs.js +693 -0
- package/dist/api/configs.js.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +24 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/mcp.d.ts +7 -0
- package/dist/api/mcp.d.ts.map +1 -0
- package/dist/api/mcp.js +46 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/middleware.d.ts +25 -0
- package/dist/api/middleware.d.ts.map +1 -0
- package/dist/api/middleware.js +65 -0
- package/dist/api/middleware.js.map +1 -0
- package/dist/api/prompts.d.ts +6 -0
- package/dist/api/prompts.d.ts.map +1 -0
- package/dist/api/prompts.js +260 -0
- package/dist/api/prompts.js.map +1 -0
- package/dist/api/resources.d.ts +6 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +159 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/tables.d.ts +15 -0
- package/dist/api/tables.d.ts.map +1 -0
- package/dist/api/tables.js +702 -0
- package/dist/api/tables.js.map +1 -0
- package/dist/api/users.d.ts +6 -0
- package/dist/api/users.d.ts.map +1 -0
- package/dist/api/users.js +96 -0
- package/dist/api/users.js.map +1 -0
- package/dist/auth/index.d.ts +15 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +126 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +288 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +164 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +646 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +73 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/aggregator.d.ts +78 -0
- package/dist/mcp/aggregator.d.ts.map +1 -0
- package/dist/mcp/aggregator.js +1266 -0
- package/dist/mcp/aggregator.js.map +1 -0
- package/dist/mcp/component-loader.d.ts +17 -0
- package/dist/mcp/component-loader.d.ts.map +1 -0
- package/dist/mcp/component-loader.js +131 -0
- package/dist/mcp/component-loader.js.map +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +107 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-client.d.ts +53 -0
- package/dist/mcp/mcp-client.d.ts.map +1 -0
- package/dist/mcp/mcp-client.js +418 -0
- package/dist/mcp/mcp-client.js.map +1 -0
- package/dist/mcp/post-sse-transport.d.ts +52 -0
- package/dist/mcp/post-sse-transport.d.ts.map +1 -0
- package/dist/mcp/post-sse-transport.js +375 -0
- package/dist/mcp/post-sse-transport.js.map +1 -0
- package/dist/mcp/service.d.ts +49 -0
- package/dist/mcp/service.d.ts.map +1 -0
- package/dist/mcp/service.js +358 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/tool-sync.d.ts +27 -0
- package/dist/mcp/tool-sync.d.ts.map +1 -0
- package/dist/mcp/tool-sync.js +200 -0
- package/dist/mcp/tool-sync.js.map +1 -0
- package/dist/script/compiler.d.ts +15 -0
- package/dist/script/compiler.d.ts.map +1 -0
- package/dist/script/compiler.js +163 -0
- package/dist/script/compiler.js.map +1 -0
- package/dist/script/context.d.ts +11 -0
- package/dist/script/context.d.ts.map +1 -0
- package/dist/script/context.js +786 -0
- package/dist/script/context.js.map +1 -0
- package/dist/script/executor.d.ts +43 -0
- package/dist/script/executor.d.ts.map +1 -0
- package/dist/script/executor.js +126 -0
- package/dist/script/executor.js.map +1 -0
- package/dist/static.d.ts +21 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +95 -0
- package/dist/static.js.map +1 -0
- package/dist/types/index.d.ts +337 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/id.d.ts +5 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +8 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +31 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompt-template.d.ts +15 -0
- package/dist/utils/prompt-template.d.ts.map +1 -0
- package/dist/utils/prompt-template.js +82 -0
- package/dist/utils/prompt-template.js.map +1 -0
- package/dist/utils/telemetry.d.ts +45 -0
- package/dist/utils/telemetry.d.ts.map +1 -0
- package/dist/utils/telemetry.js +245 -0
- package/dist/utils/telemetry.js.map +1 -0
- package/dist/utils/variables.d.ts +24 -0
- package/dist/utils/variables.d.ts.map +1 -0
- package/dist/utils/variables.js +45 -0
- package/dist/utils/variables.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表管理 API
|
|
3
|
+
*/
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
import { authMiddleware, getCurrentUserId, getCurrentUserRole } from './middleware.js';
|
|
6
|
+
import { db } from '../db/index.js';
|
|
7
|
+
import { tableDefinitions, tableData } from '../db/schema.js';
|
|
8
|
+
import { eq, and } from 'drizzle-orm';
|
|
9
|
+
import { generateId } from '../utils/id.js';
|
|
10
|
+
import { report } from '../utils/telemetry.js';
|
|
11
|
+
export const tablesRouter = new Hono();
|
|
12
|
+
// 所有路由需要认证
|
|
13
|
+
tablesRouter.use('*', authMiddleware);
|
|
14
|
+
// 中间件:禁止超级管理员访问数据管理功能
|
|
15
|
+
tablesRouter.use('*', async (c, next) => {
|
|
16
|
+
const currentRole = getCurrentUserRole(c);
|
|
17
|
+
if (currentRole === 'admin') {
|
|
18
|
+
return c.json({ success: false, error: '超级管理员不允许访问数据管理功能' }, 403);
|
|
19
|
+
}
|
|
20
|
+
await next();
|
|
21
|
+
});
|
|
22
|
+
// 获取当前用户的所有表定义列表
|
|
23
|
+
tablesRouter.get('/definitions', async (c) => {
|
|
24
|
+
try {
|
|
25
|
+
const userId = getCurrentUserId(c);
|
|
26
|
+
const result = await db
|
|
27
|
+
.select()
|
|
28
|
+
.from(tableDefinitions)
|
|
29
|
+
.where(eq(tableDefinitions.userId, userId));
|
|
30
|
+
const tables = result.map((row) => ({
|
|
31
|
+
id: row.id,
|
|
32
|
+
name: row.name,
|
|
33
|
+
description: row.description || undefined,
|
|
34
|
+
columns: JSON.parse(row.columns),
|
|
35
|
+
allowExplore: row.allowExplore !== undefined ? Boolean(row.allowExplore) : true, // 默认为 true
|
|
36
|
+
createdAt: row.createdAt,
|
|
37
|
+
updatedAt: row.updatedAt,
|
|
38
|
+
}));
|
|
39
|
+
return c.json({ success: true, data: tables });
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// 获取单个表定义(仅限当前用户)
|
|
46
|
+
tablesRouter.get('/definitions/:id', async (c) => {
|
|
47
|
+
try {
|
|
48
|
+
const userId = getCurrentUserId(c);
|
|
49
|
+
const tableId = c.req.param('id');
|
|
50
|
+
const result = await db
|
|
51
|
+
.select()
|
|
52
|
+
.from(tableDefinitions)
|
|
53
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
54
|
+
.limit(1);
|
|
55
|
+
if (result.length === 0) {
|
|
56
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
57
|
+
}
|
|
58
|
+
const row = result[0];
|
|
59
|
+
const table = {
|
|
60
|
+
id: row.id,
|
|
61
|
+
name: row.name,
|
|
62
|
+
description: row.description || undefined,
|
|
63
|
+
columns: JSON.parse(row.columns),
|
|
64
|
+
allowExplore: row.allowExplore !== undefined ? Boolean(row.allowExplore) : true, // 默认为 true
|
|
65
|
+
createdAt: row.createdAt,
|
|
66
|
+
updatedAt: row.updatedAt,
|
|
67
|
+
};
|
|
68
|
+
return c.json({ success: true, data: table });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// 创建表定义(当前用户)
|
|
75
|
+
tablesRouter.post('/definitions', async (c) => {
|
|
76
|
+
try {
|
|
77
|
+
const userId = getCurrentUserId(c);
|
|
78
|
+
const body = await c.req.json();
|
|
79
|
+
const { name, description, columns, allowExplore } = body;
|
|
80
|
+
if (!name || !columns || !Array.isArray(columns) || columns.length === 0) {
|
|
81
|
+
return c.json({ success: false, error: 'Name and columns are required' }, 400);
|
|
82
|
+
}
|
|
83
|
+
// 验证列定义
|
|
84
|
+
for (const col of columns) {
|
|
85
|
+
if (!col.name || !col.type) {
|
|
86
|
+
return c.json({ success: false, error: 'Each column must have name and type' }, 400);
|
|
87
|
+
}
|
|
88
|
+
if (!['string', 'number', 'boolean', 'date', 'json'].includes(col.type)) {
|
|
89
|
+
return c.json({ success: false, error: `Invalid column type: ${col.type}` }, 400);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 检查当前用户的表名是否已存在
|
|
93
|
+
const existing = await db
|
|
94
|
+
.select()
|
|
95
|
+
.from(tableDefinitions)
|
|
96
|
+
.where(and(eq(tableDefinitions.name, name), eq(tableDefinitions.userId, userId)))
|
|
97
|
+
.limit(1);
|
|
98
|
+
if (existing.length > 0) {
|
|
99
|
+
return c.json({ success: false, error: 'Table with this name already exists' }, 400);
|
|
100
|
+
}
|
|
101
|
+
const tableId = generateId();
|
|
102
|
+
const now = new Date();
|
|
103
|
+
await db.insert(tableDefinitions).values({
|
|
104
|
+
id: tableId,
|
|
105
|
+
userId,
|
|
106
|
+
name,
|
|
107
|
+
description: description || null,
|
|
108
|
+
columns: JSON.stringify(columns),
|
|
109
|
+
allowExplore: allowExplore !== undefined ? allowExplore : true, // 默认为 true
|
|
110
|
+
createdAt: now,
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
});
|
|
113
|
+
// 上报表创建统计
|
|
114
|
+
report('table_created', {
|
|
115
|
+
columns_count: columns.length,
|
|
116
|
+
allow_explore: allowExplore !== false,
|
|
117
|
+
}, userId);
|
|
118
|
+
const result = await db
|
|
119
|
+
.select()
|
|
120
|
+
.from(tableDefinitions)
|
|
121
|
+
.where(eq(tableDefinitions.id, tableId))
|
|
122
|
+
.limit(1);
|
|
123
|
+
const row = result[0];
|
|
124
|
+
const table = {
|
|
125
|
+
id: row.id,
|
|
126
|
+
name: row.name,
|
|
127
|
+
description: row.description || undefined,
|
|
128
|
+
columns: JSON.parse(row.columns),
|
|
129
|
+
allowExplore: row.allowExplore !== undefined ? Boolean(row.allowExplore) : true, // 默认为 true
|
|
130
|
+
createdAt: row.createdAt,
|
|
131
|
+
updatedAt: row.updatedAt,
|
|
132
|
+
};
|
|
133
|
+
return c.json({ success: true, data: table }, 201);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// 更新表定义(仅限当前用户)
|
|
140
|
+
tablesRouter.put('/definitions/:id', async (c) => {
|
|
141
|
+
try {
|
|
142
|
+
const userId = getCurrentUserId(c);
|
|
143
|
+
const tableId = c.req.param('id');
|
|
144
|
+
const body = await c.req.json();
|
|
145
|
+
const existing = await db
|
|
146
|
+
.select()
|
|
147
|
+
.from(tableDefinitions)
|
|
148
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
149
|
+
.limit(1);
|
|
150
|
+
if (existing.length === 0) {
|
|
151
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
152
|
+
}
|
|
153
|
+
// 如果表名改变,检查当前用户的新表名是否已存在
|
|
154
|
+
if (body.name && body.name !== existing[0].name) {
|
|
155
|
+
const nameExists = await db
|
|
156
|
+
.select()
|
|
157
|
+
.from(tableDefinitions)
|
|
158
|
+
.where(and(eq(tableDefinitions.name, body.name), eq(tableDefinitions.userId, userId)))
|
|
159
|
+
.limit(1);
|
|
160
|
+
if (nameExists.length > 0) {
|
|
161
|
+
return c.json({ success: false, error: 'Table with this name already exists' }, 400);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// 如果列定义改变,验证列定义
|
|
165
|
+
if (body.columns) {
|
|
166
|
+
if (!Array.isArray(body.columns) || body.columns.length === 0) {
|
|
167
|
+
return c.json({ success: false, error: 'Columns must be a non-empty array' }, 400);
|
|
168
|
+
}
|
|
169
|
+
for (const col of body.columns) {
|
|
170
|
+
if (!col.name || !col.type) {
|
|
171
|
+
return c.json({ success: false, error: 'Each column must have name and type' }, 400);
|
|
172
|
+
}
|
|
173
|
+
if (!['string', 'number', 'boolean', 'date', 'json'].includes(col.type)) {
|
|
174
|
+
return c.json({ success: false, error: `Invalid column type: ${col.type}` }, 400);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
await db
|
|
179
|
+
.update(tableDefinitions)
|
|
180
|
+
.set({
|
|
181
|
+
name: body.name,
|
|
182
|
+
description: body.description !== undefined ? body.description : null,
|
|
183
|
+
columns: body.columns ? JSON.stringify(body.columns) : undefined,
|
|
184
|
+
allowExplore: body.allowExplore !== undefined ? body.allowExplore : undefined,
|
|
185
|
+
updatedAt: new Date(),
|
|
186
|
+
})
|
|
187
|
+
.where(eq(tableDefinitions.id, tableId));
|
|
188
|
+
const result = await db
|
|
189
|
+
.select()
|
|
190
|
+
.from(tableDefinitions)
|
|
191
|
+
.where(eq(tableDefinitions.id, tableId))
|
|
192
|
+
.limit(1);
|
|
193
|
+
const row = result[0];
|
|
194
|
+
const table = {
|
|
195
|
+
id: row.id,
|
|
196
|
+
name: row.name,
|
|
197
|
+
description: row.description || undefined,
|
|
198
|
+
columns: JSON.parse(row.columns),
|
|
199
|
+
createdAt: row.createdAt,
|
|
200
|
+
updatedAt: row.updatedAt,
|
|
201
|
+
};
|
|
202
|
+
return c.json({ success: true, data: table });
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
// 删除表定义(仅限当前用户,会级联删除所有数据)
|
|
209
|
+
tablesRouter.delete('/definitions/:id', async (c) => {
|
|
210
|
+
try {
|
|
211
|
+
const userId = getCurrentUserId(c);
|
|
212
|
+
const tableId = c.req.param('id');
|
|
213
|
+
const existing = await db
|
|
214
|
+
.select()
|
|
215
|
+
.from(tableDefinitions)
|
|
216
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
217
|
+
.limit(1);
|
|
218
|
+
if (existing.length === 0) {
|
|
219
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
220
|
+
}
|
|
221
|
+
await db.delete(tableDefinitions).where(eq(tableDefinitions.id, tableId));
|
|
222
|
+
return c.json({ success: true, message: 'Table definition deleted' });
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
// 获取表的所有数据(仅限当前用户的表)
|
|
229
|
+
tablesRouter.get('/data/:tableId', async (c) => {
|
|
230
|
+
try {
|
|
231
|
+
const userId = getCurrentUserId(c);
|
|
232
|
+
const tableId = c.req.param('tableId');
|
|
233
|
+
// 验证表定义是否存在且属于当前用户
|
|
234
|
+
const tableDef = await db
|
|
235
|
+
.select()
|
|
236
|
+
.from(tableDefinitions)
|
|
237
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
238
|
+
.limit(1);
|
|
239
|
+
if (tableDef.length === 0) {
|
|
240
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
241
|
+
}
|
|
242
|
+
const result = await db
|
|
243
|
+
.select()
|
|
244
|
+
.from(tableData)
|
|
245
|
+
.where(eq(tableData.tableId, tableId));
|
|
246
|
+
const rows = result.map((row) => ({
|
|
247
|
+
id: row.id,
|
|
248
|
+
tableId: row.tableId,
|
|
249
|
+
rowData: JSON.parse(row.rowData),
|
|
250
|
+
createdAt: row.createdAt,
|
|
251
|
+
updatedAt: row.updatedAt,
|
|
252
|
+
}));
|
|
253
|
+
return c.json({ success: true, data: rows });
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
// 创建表数据行(仅限当前用户的表)
|
|
260
|
+
tablesRouter.post('/data/:tableId', async (c) => {
|
|
261
|
+
try {
|
|
262
|
+
const userId = getCurrentUserId(c);
|
|
263
|
+
const tableId = c.req.param('tableId');
|
|
264
|
+
const body = await c.req.json();
|
|
265
|
+
// 验证表定义是否存在且属于当前用户
|
|
266
|
+
const tableDef = await db
|
|
267
|
+
.select()
|
|
268
|
+
.from(tableDefinitions)
|
|
269
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
270
|
+
.limit(1);
|
|
271
|
+
if (tableDef.length === 0) {
|
|
272
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
273
|
+
}
|
|
274
|
+
const columns = JSON.parse(tableDef[0].columns);
|
|
275
|
+
// 验证数据是否符合列定义
|
|
276
|
+
const validatedData = {};
|
|
277
|
+
for (const col of columns) {
|
|
278
|
+
const value = body.rowData[col.name];
|
|
279
|
+
if (value === undefined || value === null) {
|
|
280
|
+
if (col.nullable) {
|
|
281
|
+
validatedData[col.name] = null;
|
|
282
|
+
}
|
|
283
|
+
else if (col.defaultValue !== undefined) {
|
|
284
|
+
validatedData[col.name] = col.defaultValue;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
return c.json({ success: false, error: `Column ${col.name} is required` }, 400);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// 类型转换和验证
|
|
292
|
+
validatedData[col.name] = convertValue(value, col.type);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const rowId = generateId();
|
|
296
|
+
const now = new Date();
|
|
297
|
+
await db.insert(tableData).values({
|
|
298
|
+
id: rowId,
|
|
299
|
+
tableId,
|
|
300
|
+
rowData: JSON.stringify(validatedData),
|
|
301
|
+
createdAt: now,
|
|
302
|
+
updatedAt: now,
|
|
303
|
+
});
|
|
304
|
+
const result = await db
|
|
305
|
+
.select()
|
|
306
|
+
.from(tableData)
|
|
307
|
+
.where(eq(tableData.id, rowId))
|
|
308
|
+
.limit(1);
|
|
309
|
+
const row = result[0];
|
|
310
|
+
const tableRow = {
|
|
311
|
+
id: row.id,
|
|
312
|
+
tableId: row.tableId,
|
|
313
|
+
rowData: JSON.parse(row.rowData),
|
|
314
|
+
createdAt: row.createdAt,
|
|
315
|
+
updatedAt: row.updatedAt,
|
|
316
|
+
};
|
|
317
|
+
return c.json({ success: true, data: tableRow }, 201);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// 批量更新表数据(用于 Excel 式的批量编辑,仅限当前用户的表)
|
|
324
|
+
// 注意:这个路由必须在 /data/:tableId/:rowId 之前定义,否则会被误匹配
|
|
325
|
+
tablesRouter.put('/data/:tableId/batch', async (c) => {
|
|
326
|
+
console.log('批量导入请求:', {
|
|
327
|
+
method: c.req.method,
|
|
328
|
+
path: c.req.path,
|
|
329
|
+
tableId: c.req.param('tableId'),
|
|
330
|
+
userId: getCurrentUserId(c),
|
|
331
|
+
});
|
|
332
|
+
try {
|
|
333
|
+
const userId = getCurrentUserId(c);
|
|
334
|
+
const tableId = c.req.param('tableId');
|
|
335
|
+
const body = await c.req.json();
|
|
336
|
+
console.log('批量导入请求体:', {
|
|
337
|
+
rowsCount: body.rows?.length,
|
|
338
|
+
sampleRow: body.rows?.[0],
|
|
339
|
+
hasIds: body.rows?.some(r => r.id !== undefined),
|
|
340
|
+
});
|
|
341
|
+
// 验证表定义是否存在且属于当前用户
|
|
342
|
+
const tableDef = await db
|
|
343
|
+
.select()
|
|
344
|
+
.from(tableDefinitions)
|
|
345
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
346
|
+
.limit(1);
|
|
347
|
+
if (tableDef.length === 0) {
|
|
348
|
+
console.error('批量导入失败:表定义不存在', {
|
|
349
|
+
tableId,
|
|
350
|
+
userId,
|
|
351
|
+
requestPath: c.req.path,
|
|
352
|
+
requestMethod: c.req.method,
|
|
353
|
+
});
|
|
354
|
+
return c.json({
|
|
355
|
+
success: false,
|
|
356
|
+
error: `Table definition not found for tableId: ${tableId}. Please check if the table exists and belongs to the current user.`
|
|
357
|
+
}, 404);
|
|
358
|
+
}
|
|
359
|
+
const columns = JSON.parse(tableDef[0].columns);
|
|
360
|
+
console.log('批量导入:表定义验证通过', {
|
|
361
|
+
tableId,
|
|
362
|
+
userId,
|
|
363
|
+
columnsCount: columns.length,
|
|
364
|
+
tableName: tableDef[0].name,
|
|
365
|
+
});
|
|
366
|
+
const now = new Date();
|
|
367
|
+
const results = [];
|
|
368
|
+
// 处理每一行
|
|
369
|
+
for (const rowInput of body.rows) {
|
|
370
|
+
// 调试日志
|
|
371
|
+
if (rowInput.id) {
|
|
372
|
+
console.log(`批量导入:检测到 id 字段 ${rowInput.id},将尝试更新现有行`);
|
|
373
|
+
}
|
|
374
|
+
if (rowInput.id) {
|
|
375
|
+
// 更新现有行
|
|
376
|
+
const existingRow = await db
|
|
377
|
+
.select()
|
|
378
|
+
.from(tableData)
|
|
379
|
+
.where(and(eq(tableData.id, rowInput.id), eq(tableData.tableId, tableId)))
|
|
380
|
+
.limit(1);
|
|
381
|
+
if (existingRow.length > 0) {
|
|
382
|
+
const currentData = JSON.parse(existingRow[0].rowData);
|
|
383
|
+
const validatedData = { ...currentData };
|
|
384
|
+
for (const col of columns) {
|
|
385
|
+
if (Object.prototype.hasOwnProperty.call(rowInput.rowData, col.name)) {
|
|
386
|
+
const value = rowInput.rowData[col.name];
|
|
387
|
+
if (value === undefined || value === null) {
|
|
388
|
+
if (col.nullable) {
|
|
389
|
+
validatedData[col.name] = null;
|
|
390
|
+
}
|
|
391
|
+
else if (col.defaultValue !== undefined) {
|
|
392
|
+
validatedData[col.name] = col.defaultValue;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
validatedData[col.name] = convertValue(value, col.type);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
await db
|
|
401
|
+
.update(tableData)
|
|
402
|
+
.set({
|
|
403
|
+
rowData: JSON.stringify(validatedData),
|
|
404
|
+
updatedAt: now,
|
|
405
|
+
})
|
|
406
|
+
.where(eq(tableData.id, rowInput.id));
|
|
407
|
+
const updated = await db
|
|
408
|
+
.select()
|
|
409
|
+
.from(tableData)
|
|
410
|
+
.where(eq(tableData.id, rowInput.id))
|
|
411
|
+
.limit(1);
|
|
412
|
+
if (updated.length === 0) {
|
|
413
|
+
throw new Error(`Failed to retrieve updated row with id: ${rowInput.id}`);
|
|
414
|
+
}
|
|
415
|
+
results.push({
|
|
416
|
+
id: updated[0].id,
|
|
417
|
+
tableId: updated[0].tableId,
|
|
418
|
+
rowData: JSON.parse(updated[0].rowData),
|
|
419
|
+
createdAt: updated[0].createdAt,
|
|
420
|
+
updatedAt: updated[0].updatedAt,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
// 行不存在,但提供了id,可能是数据不同步,创建新行
|
|
425
|
+
const validatedData = {};
|
|
426
|
+
for (const col of columns) {
|
|
427
|
+
const value = rowInput.rowData[col.name];
|
|
428
|
+
if (value === undefined || value === null) {
|
|
429
|
+
if (col.nullable) {
|
|
430
|
+
validatedData[col.name] = null;
|
|
431
|
+
}
|
|
432
|
+
else if (col.defaultValue !== undefined) {
|
|
433
|
+
validatedData[col.name] = col.defaultValue;
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// 必填字段缺失,使用默认值
|
|
437
|
+
switch (col.type) {
|
|
438
|
+
case 'string':
|
|
439
|
+
validatedData[col.name] = '';
|
|
440
|
+
break;
|
|
441
|
+
case 'number':
|
|
442
|
+
validatedData[col.name] = 0;
|
|
443
|
+
break;
|
|
444
|
+
case 'boolean':
|
|
445
|
+
validatedData[col.name] = false;
|
|
446
|
+
break;
|
|
447
|
+
case 'date':
|
|
448
|
+
validatedData[col.name] = new Date().toISOString();
|
|
449
|
+
break;
|
|
450
|
+
case 'json':
|
|
451
|
+
validatedData[col.name] = {};
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
validatedData[col.name] = convertValue(value, col.type);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const rowId = generateId();
|
|
461
|
+
await db.insert(tableData).values({
|
|
462
|
+
id: rowId,
|
|
463
|
+
tableId,
|
|
464
|
+
rowData: JSON.stringify(validatedData),
|
|
465
|
+
createdAt: now,
|
|
466
|
+
updatedAt: now,
|
|
467
|
+
});
|
|
468
|
+
const newRow = await db
|
|
469
|
+
.select()
|
|
470
|
+
.from(tableData)
|
|
471
|
+
.where(eq(tableData.id, rowId))
|
|
472
|
+
.limit(1);
|
|
473
|
+
if (newRow.length === 0) {
|
|
474
|
+
throw new Error(`Failed to retrieve newly created row with id: ${rowId}`);
|
|
475
|
+
}
|
|
476
|
+
results.push({
|
|
477
|
+
id: newRow[0].id,
|
|
478
|
+
tableId: newRow[0].tableId,
|
|
479
|
+
rowData: JSON.parse(newRow[0].rowData),
|
|
480
|
+
createdAt: newRow[0].createdAt,
|
|
481
|
+
updatedAt: newRow[0].updatedAt,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
// 创建新行
|
|
487
|
+
const validatedData = {};
|
|
488
|
+
for (const col of columns) {
|
|
489
|
+
const value = rowInput.rowData[col.name];
|
|
490
|
+
if (value === undefined || value === null) {
|
|
491
|
+
if (col.nullable) {
|
|
492
|
+
validatedData[col.name] = null;
|
|
493
|
+
}
|
|
494
|
+
else if (col.defaultValue !== undefined) {
|
|
495
|
+
validatedData[col.name] = col.defaultValue;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
// 必填字段缺失,使用默认值而不是跳过
|
|
499
|
+
switch (col.type) {
|
|
500
|
+
case 'string':
|
|
501
|
+
validatedData[col.name] = '';
|
|
502
|
+
break;
|
|
503
|
+
case 'number':
|
|
504
|
+
validatedData[col.name] = 0;
|
|
505
|
+
break;
|
|
506
|
+
case 'boolean':
|
|
507
|
+
validatedData[col.name] = false;
|
|
508
|
+
break;
|
|
509
|
+
case 'date':
|
|
510
|
+
validatedData[col.name] = new Date().toISOString();
|
|
511
|
+
break;
|
|
512
|
+
case 'json':
|
|
513
|
+
validatedData[col.name] = {};
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
validatedData[col.name] = convertValue(value, col.type);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const rowId = generateId();
|
|
523
|
+
await db.insert(tableData).values({
|
|
524
|
+
id: rowId,
|
|
525
|
+
tableId,
|
|
526
|
+
rowData: JSON.stringify(validatedData),
|
|
527
|
+
createdAt: now,
|
|
528
|
+
updatedAt: now,
|
|
529
|
+
});
|
|
530
|
+
const newRow = await db
|
|
531
|
+
.select()
|
|
532
|
+
.from(tableData)
|
|
533
|
+
.where(eq(tableData.id, rowId))
|
|
534
|
+
.limit(1);
|
|
535
|
+
if (newRow.length === 0) {
|
|
536
|
+
throw new Error(`Failed to retrieve newly created row with id: ${rowId}`);
|
|
537
|
+
}
|
|
538
|
+
results.push({
|
|
539
|
+
id: newRow[0].id,
|
|
540
|
+
tableId: newRow[0].tableId,
|
|
541
|
+
rowData: JSON.parse(newRow[0].rowData),
|
|
542
|
+
createdAt: newRow[0].createdAt,
|
|
543
|
+
updatedAt: newRow[0].updatedAt,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return c.json({ success: true, data: results });
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
// 更新表数据行(仅限当前用户的表)
|
|
554
|
+
tablesRouter.put('/data/:tableId/:rowId', async (c) => {
|
|
555
|
+
try {
|
|
556
|
+
const userId = getCurrentUserId(c);
|
|
557
|
+
const tableId = c.req.param('tableId');
|
|
558
|
+
const rowId = c.req.param('rowId');
|
|
559
|
+
const body = await c.req.json();
|
|
560
|
+
// 验证表定义是否存在且属于当前用户
|
|
561
|
+
const tableDef = await db
|
|
562
|
+
.select()
|
|
563
|
+
.from(tableDefinitions)
|
|
564
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
565
|
+
.limit(1);
|
|
566
|
+
if (tableDef.length === 0) {
|
|
567
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
568
|
+
}
|
|
569
|
+
// 验证行是否存在
|
|
570
|
+
const existingRow = await db
|
|
571
|
+
.select()
|
|
572
|
+
.from(tableData)
|
|
573
|
+
.where(and(eq(tableData.id, rowId), eq(tableData.tableId, tableId)))
|
|
574
|
+
.limit(1);
|
|
575
|
+
if (existingRow.length === 0) {
|
|
576
|
+
return c.json({ success: false, error: 'Row not found' }, 404);
|
|
577
|
+
}
|
|
578
|
+
const columns = JSON.parse(tableDef[0].columns);
|
|
579
|
+
const currentData = JSON.parse(existingRow[0].rowData);
|
|
580
|
+
// 合并并验证数据
|
|
581
|
+
const validatedData = { ...currentData };
|
|
582
|
+
for (const col of columns) {
|
|
583
|
+
if (Object.prototype.hasOwnProperty.call(body.rowData, col.name)) {
|
|
584
|
+
const value = body.rowData[col.name];
|
|
585
|
+
if (value === undefined || value === null) {
|
|
586
|
+
if (col.nullable) {
|
|
587
|
+
validatedData[col.name] = null;
|
|
588
|
+
}
|
|
589
|
+
else if (col.defaultValue !== undefined) {
|
|
590
|
+
validatedData[col.name] = col.defaultValue;
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
return c.json({ success: false, error: `Column ${col.name} cannot be null` }, 400);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
validatedData[col.name] = convertValue(value, col.type);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
await db
|
|
602
|
+
.update(tableData)
|
|
603
|
+
.set({
|
|
604
|
+
rowData: JSON.stringify(validatedData),
|
|
605
|
+
updatedAt: new Date(),
|
|
606
|
+
})
|
|
607
|
+
.where(eq(tableData.id, rowId));
|
|
608
|
+
const result = await db
|
|
609
|
+
.select()
|
|
610
|
+
.from(tableData)
|
|
611
|
+
.where(eq(tableData.id, rowId))
|
|
612
|
+
.limit(1);
|
|
613
|
+
const row = result[0];
|
|
614
|
+
const tableRow = {
|
|
615
|
+
id: row.id,
|
|
616
|
+
tableId: row.tableId,
|
|
617
|
+
rowData: JSON.parse(row.rowData),
|
|
618
|
+
createdAt: row.createdAt,
|
|
619
|
+
updatedAt: row.updatedAt,
|
|
620
|
+
};
|
|
621
|
+
return c.json({ success: true, data: tableRow });
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
// 删除表数据行(仅限当前用户的表)
|
|
628
|
+
tablesRouter.delete('/data/:tableId/:rowId', async (c) => {
|
|
629
|
+
try {
|
|
630
|
+
const userId = getCurrentUserId(c);
|
|
631
|
+
const tableId = c.req.param('tableId');
|
|
632
|
+
const rowId = c.req.param('rowId');
|
|
633
|
+
// 先验证表是否属于当前用户
|
|
634
|
+
const tableDef = await db
|
|
635
|
+
.select()
|
|
636
|
+
.from(tableDefinitions)
|
|
637
|
+
.where(and(eq(tableDefinitions.id, tableId), eq(tableDefinitions.userId, userId)))
|
|
638
|
+
.limit(1);
|
|
639
|
+
if (tableDef.length === 0) {
|
|
640
|
+
return c.json({ success: false, error: 'Table definition not found' }, 404);
|
|
641
|
+
}
|
|
642
|
+
const existing = await db
|
|
643
|
+
.select()
|
|
644
|
+
.from(tableData)
|
|
645
|
+
.where(and(eq(tableData.id, rowId), eq(tableData.tableId, tableId)))
|
|
646
|
+
.limit(1);
|
|
647
|
+
if (existing.length === 0) {
|
|
648
|
+
return c.json({ success: false, error: 'Row not found' }, 404);
|
|
649
|
+
}
|
|
650
|
+
await db.delete(tableData).where(eq(tableData.id, rowId));
|
|
651
|
+
return c.json({ success: true, message: 'Row deleted' });
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
return c.json({ success: false, error: error.message }, 500);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
// 辅助函数:类型转换
|
|
658
|
+
function convertValue(value, type) {
|
|
659
|
+
if (value === null || value === undefined) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
switch (type) {
|
|
663
|
+
case 'string':
|
|
664
|
+
return String(value);
|
|
665
|
+
case 'number':
|
|
666
|
+
return Number(value);
|
|
667
|
+
case 'boolean':
|
|
668
|
+
if (typeof value === 'boolean') {
|
|
669
|
+
return value;
|
|
670
|
+
}
|
|
671
|
+
if (typeof value === 'string') {
|
|
672
|
+
return value.toLowerCase() === 'true' || value === '1';
|
|
673
|
+
}
|
|
674
|
+
return Boolean(value);
|
|
675
|
+
case 'date': {
|
|
676
|
+
if (value instanceof Date) {
|
|
677
|
+
return value.toISOString();
|
|
678
|
+
}
|
|
679
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
680
|
+
const date = new Date(value);
|
|
681
|
+
if (isNaN(date.getTime())) {
|
|
682
|
+
throw new Error(`Cannot convert "${value}" to date`);
|
|
683
|
+
}
|
|
684
|
+
return date.toISOString();
|
|
685
|
+
}
|
|
686
|
+
throw new Error(`Cannot convert "${value}" to date`);
|
|
687
|
+
}
|
|
688
|
+
case 'json':
|
|
689
|
+
if (typeof value === 'string') {
|
|
690
|
+
try {
|
|
691
|
+
return JSON.parse(value);
|
|
692
|
+
}
|
|
693
|
+
catch {
|
|
694
|
+
return value;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return value;
|
|
698
|
+
default:
|
|
699
|
+
return value;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
//# sourceMappingURL=tables.js.map
|