xinyu-pro 0.21.4 → 0.22.1
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/.env.example +2 -6
- package/app/api/plugins/bindings/route.ts +169 -173
- package/app/api/plugins/export/route.ts +5 -6
- package/app/api/plugins/export-xye/route.ts +3 -4
- package/app/api/plugins/files-list/route.ts +3 -4
- package/app/api/plugins/files-upload/route.ts +6 -10
- package/app/api/plugins/files-write/route.ts +20 -23
- package/app/api/plugins/import/route.ts +121 -140
- package/app/api/plugins/import-package/route.ts +214 -231
- package/app/api/plugins/resources/route.ts +3 -4
- package/app/api/plugins/route.ts +309 -308
- package/app/api/plugins/scan/route.ts +2 -3
- package/app/api/plugins/storage/route.ts +138 -146
- package/app/api/sessions/route.ts +137 -165
- package/app/api/settings/route.ts +39 -40
- package/app/api/templates/route.ts +135 -159
- package/bin/cli.js +2 -13
- package/lib/db-init.ts +0 -12
- package/lib/db.ts +205 -244
- package/next.config.mjs +6 -0
- package/package.json +2 -2
- package/public/sql-wasm.wasm +0 -0
- package/version.json +1 -1
package/.env.example
CHANGED
|
@@ -1,173 +1,169 @@
|
|
|
1
|
-
// app/api/plugins/bindings/route.ts - 插件绑定 API
|
|
2
|
-
|
|
3
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import {
|
|
5
|
-
import { ensureDb } from '@/lib/db-init';
|
|
6
|
-
|
|
7
|
-
/** 解析 JSON 字段 */
|
|
8
|
-
function parseJsonField<T>(value: unknown): T {
|
|
9
|
-
if (typeof value === 'string') {
|
|
10
|
-
try { return JSON.parse(value) as T; } catch { return value as unknown as T; }
|
|
11
|
-
}
|
|
12
|
-
return value as T;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** 行数据转换为绑定对象 */
|
|
16
|
-
function rowToBinding(row: Record<string, unknown>) {
|
|
17
|
-
return {
|
|
18
|
-
id: row.id,
|
|
19
|
-
extensionId: row.extension_id,
|
|
20
|
-
scope: row.scope,
|
|
21
|
-
worldId: row.world_id,
|
|
22
|
-
enabled: Boolean(row.enabled),
|
|
23
|
-
config: parseJsonField<Record<string, unknown>>(row.config),
|
|
24
|
-
sortOrder: row.sort_order,
|
|
25
|
-
createdAt: row.created_at,
|
|
26
|
-
updatedAt: row.updated_at,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// GET: 获取绑定列表
|
|
31
|
-
export async function GET(request: NextRequest) {
|
|
32
|
-
try {
|
|
33
|
-
await ensureDb();
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
await
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return NextResponse.json({
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
console.error('删除插件绑定失败:', e);
|
|
171
|
-
return NextResponse.json({ error: '删除插件绑定失败' }, { status: 500 });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
1
|
+
// app/api/plugins/bindings/route.ts - 插件绑定 API
|
|
2
|
+
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
import { execute } from '@/lib/db';
|
|
5
|
+
import { ensureDb } from '@/lib/db-init';
|
|
6
|
+
|
|
7
|
+
/** 解析 JSON 字段 */
|
|
8
|
+
function parseJsonField<T>(value: unknown): T {
|
|
9
|
+
if (typeof value === 'string') {
|
|
10
|
+
try { return JSON.parse(value) as T; } catch { return value as unknown as T; }
|
|
11
|
+
}
|
|
12
|
+
return value as T;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 行数据转换为绑定对象 */
|
|
16
|
+
function rowToBinding(row: Record<string, unknown>) {
|
|
17
|
+
return {
|
|
18
|
+
id: row.id,
|
|
19
|
+
extensionId: row.extension_id,
|
|
20
|
+
scope: row.scope,
|
|
21
|
+
worldId: row.world_id,
|
|
22
|
+
enabled: Boolean(row.enabled),
|
|
23
|
+
config: parseJsonField<Record<string, unknown>>(row.config),
|
|
24
|
+
sortOrder: row.sort_order,
|
|
25
|
+
createdAt: row.created_at,
|
|
26
|
+
updatedAt: row.updated_at,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// GET: 获取绑定列表
|
|
31
|
+
export async function GET(request: NextRequest) {
|
|
32
|
+
try {
|
|
33
|
+
await ensureDb();
|
|
34
|
+
const { searchParams } = new URL(request.url);
|
|
35
|
+
const id = searchParams.get('id');
|
|
36
|
+
const scope = searchParams.get('scope');
|
|
37
|
+
const worldId = searchParams.get('worldId');
|
|
38
|
+
|
|
39
|
+
if (id) {
|
|
40
|
+
const [rows] = await execute(
|
|
41
|
+
'SELECT * FROM extension_bindings WHERE id = ?',
|
|
42
|
+
[id]
|
|
43
|
+
);
|
|
44
|
+
if ((rows as Record<string, unknown>[]).length === 0) {
|
|
45
|
+
return NextResponse.json({ error: '绑定不存在' }, { status: 404 });
|
|
46
|
+
}
|
|
47
|
+
return NextResponse.json(rowToBinding((rows as Record<string, unknown>[])[0]));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let query = 'SELECT * FROM extension_bindings WHERE 1=1';
|
|
51
|
+
const params: (string | null)[] = [];
|
|
52
|
+
|
|
53
|
+
if (scope) {
|
|
54
|
+
query += ' AND scope = ?';
|
|
55
|
+
params.push(scope);
|
|
56
|
+
}
|
|
57
|
+
if (worldId) {
|
|
58
|
+
query += ' AND world_id = ?';
|
|
59
|
+
params.push(worldId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
query += ' ORDER BY sort_order ASC, id ASC';
|
|
63
|
+
const [rows] = await execute(query, params);
|
|
64
|
+
return NextResponse.json((rows as Record<string, unknown>[]).map(rowToBinding));
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error('获取插件绑定失败:', e);
|
|
67
|
+
return NextResponse.json({ error: '获取插件绑定失败' }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// POST: 创建或更新绑定(upsert)
|
|
72
|
+
export async function POST(request: NextRequest) {
|
|
73
|
+
try {
|
|
74
|
+
await ensureDb();
|
|
75
|
+
const body = await request.json();
|
|
76
|
+
const { extensionId, scope, worldId, enabled, config, sortOrder } = body;
|
|
77
|
+
|
|
78
|
+
if (!extensionId) {
|
|
79
|
+
return NextResponse.json({ error: '缺少插件 ID' }, { status: 400 });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const bindingScope = scope ?? 'global';
|
|
83
|
+
const bindingWorldId = worldId ?? '';
|
|
84
|
+
const bindingEnabled = enabled !== undefined ? enabled : true;
|
|
85
|
+
const bindingConfig = config ?? {};
|
|
86
|
+
const bindingSortOrder = sortOrder ?? 0;
|
|
87
|
+
|
|
88
|
+
await execute(
|
|
89
|
+
`INSERT INTO extension_bindings (extension_id, scope, world_id, enabled, config, sort_order)
|
|
90
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
91
|
+
ON CONFLICT(extension_id, scope, world_id) DO UPDATE SET
|
|
92
|
+
enabled = excluded.enabled, config = excluded.config, sort_order = excluded.sort_order`,
|
|
93
|
+
[extensionId, bindingScope, bindingWorldId, bindingEnabled, JSON.stringify(bindingConfig), bindingSortOrder]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return NextResponse.json({ success: true, extensionId });
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error('创建插件绑定失败:', e);
|
|
99
|
+
return NextResponse.json({ error: '创建插件绑定失败' }, { status: 500 });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// PUT: 更新绑定
|
|
104
|
+
export async function PUT(request: NextRequest) {
|
|
105
|
+
try {
|
|
106
|
+
await ensureDb();
|
|
107
|
+
const body = await request.json();
|
|
108
|
+
const { id, ...fields } = body;
|
|
109
|
+
|
|
110
|
+
if (!id) {
|
|
111
|
+
return NextResponse.json({ error: '缺少绑定 ID' }, { status: 400 });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const updates: string[] = [];
|
|
115
|
+
const params: (string | number | boolean | null)[] = [];
|
|
116
|
+
|
|
117
|
+
const allowedFields = ['enabled', 'config', 'sortOrder', 'scope', 'worldId'];
|
|
118
|
+
const columnMap: Record<string, string> = {
|
|
119
|
+
sortOrder: 'sort_order',
|
|
120
|
+
worldId: 'world_id',
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
for (const field of allowedFields) {
|
|
124
|
+
if (fields[field] !== undefined) {
|
|
125
|
+
const col = columnMap[field] || field;
|
|
126
|
+
updates.push(`${col} = ?`);
|
|
127
|
+
if (field === 'config') {
|
|
128
|
+
params.push(JSON.stringify(fields[field]));
|
|
129
|
+
} else {
|
|
130
|
+
params.push(fields[field]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (updates.length === 0) {
|
|
136
|
+
return NextResponse.json({ error: '没有需要更新的字段' }, { status: 400 });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
params.push(id);
|
|
140
|
+
await execute(
|
|
141
|
+
`UPDATE extension_bindings SET ${updates.join(', ')} WHERE id = ?`,
|
|
142
|
+
params
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return NextResponse.json({ success: true, id });
|
|
146
|
+
} catch (e) {
|
|
147
|
+
console.error('更新插件绑定失败:', e);
|
|
148
|
+
return NextResponse.json({ error: '更新插件绑定失败' }, { status: 500 });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// DELETE: 删除绑定
|
|
153
|
+
export async function DELETE(request: NextRequest) {
|
|
154
|
+
try {
|
|
155
|
+
await ensureDb();
|
|
156
|
+
const { searchParams } = new URL(request.url);
|
|
157
|
+
const id = searchParams.get('id');
|
|
158
|
+
|
|
159
|
+
if (!id) {
|
|
160
|
+
return NextResponse.json({ error: '缺少绑定 ID' }, { status: 400 });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await execute('DELETE FROM extension_bindings WHERE id = ?', [id]);
|
|
164
|
+
return NextResponse.json({ success: true, id });
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error('删除插件绑定失败:', e);
|
|
167
|
+
return NextResponse.json({ error: '删除插件绑定失败' }, { status: 500 });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// app/api/plugins/export/route.ts - 插件导出 API
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import {
|
|
4
|
+
import { execute } from '@/lib/db';
|
|
5
5
|
import { ensureDb } from '@/lib/db-init';
|
|
6
6
|
import { readPluginCode } from '@/lib/plugin-files';
|
|
7
7
|
|
|
@@ -70,17 +70,16 @@ async function fillPluginCode(plugin: ReturnType<typeof rowToPlugin>): Promise<t
|
|
|
70
70
|
export async function GET(request: NextRequest) {
|
|
71
71
|
try {
|
|
72
72
|
await ensureDb();
|
|
73
|
-
const db = getDbPool();
|
|
74
73
|
|
|
75
74
|
const { searchParams } = new URL(request.url);
|
|
76
75
|
const pluginId = searchParams.get('id');
|
|
77
76
|
|
|
78
77
|
if (pluginId) {
|
|
79
78
|
// 导出单个插件(直接返回插件对象,不带 format 包裹)
|
|
80
|
-
const [rows] = await
|
|
79
|
+
const [rows] = await execute(
|
|
81
80
|
'SELECT * FROM extensions WHERE id = ?',
|
|
82
81
|
[pluginId]
|
|
83
|
-
)
|
|
82
|
+
);
|
|
84
83
|
|
|
85
84
|
if (rows.length === 0) {
|
|
86
85
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
@@ -97,9 +96,9 @@ export async function GET(request: NextRequest) {
|
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
// 导出全部插件(多插件包格式)
|
|
100
|
-
const [rows] = await
|
|
99
|
+
const [rows] = await execute(
|
|
101
100
|
'SELECT * FROM extensions ORDER BY updated_at DESC'
|
|
102
|
-
)
|
|
101
|
+
);
|
|
103
102
|
|
|
104
103
|
const plugins = await Promise.all(rows.map(r => fillPluginCode(rowToPlugin(r))));
|
|
105
104
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// app/api/plugins/export-xye/route.ts - 导出插件为 .xye 包(ZIP 格式)
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import {
|
|
4
|
+
import { execute } from '@/lib/db';
|
|
5
5
|
import { ensureDb } from '@/lib/db-init';
|
|
6
6
|
import { getPluginDir, readPluginCode } from '@/lib/plugin-files';
|
|
7
7
|
import { listPluginFiles } from '@/lib/plugin-files';
|
|
@@ -41,7 +41,6 @@ function parsePermissions(value: unknown): {
|
|
|
41
41
|
export async function GET(request: NextRequest) {
|
|
42
42
|
try {
|
|
43
43
|
await ensureDb();
|
|
44
|
-
const db = getDbPool();
|
|
45
44
|
|
|
46
45
|
const { searchParams } = new URL(request.url);
|
|
47
46
|
const pluginId = searchParams.get('id');
|
|
@@ -50,10 +49,10 @@ export async function GET(request: NextRequest) {
|
|
|
50
49
|
return NextResponse.json({ error: '缺少 id 参数' }, { status: 400 });
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
const [rows] = await
|
|
52
|
+
const [rows] = await execute(
|
|
54
53
|
'SELECT * FROM extensions WHERE id = ?',
|
|
55
54
|
[pluginId]
|
|
56
|
-
)
|
|
55
|
+
);
|
|
57
56
|
|
|
58
57
|
if (rows.length === 0) {
|
|
59
58
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
4
|
import { ensureDb } from '@/lib/db-init';
|
|
5
|
-
import {
|
|
5
|
+
import { execute } from '@/lib/db';
|
|
6
6
|
import { listPluginFiles, readPluginResourceText } from '@/lib/plugin-files';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
|
|
@@ -56,11 +56,10 @@ export async function GET(request: NextRequest) {
|
|
|
56
56
|
|
|
57
57
|
// 验证插件存在
|
|
58
58
|
await ensureDb();
|
|
59
|
-
const
|
|
60
|
-
const [rows] = await db.execute(
|
|
59
|
+
const [rows] = await execute(
|
|
61
60
|
'SELECT id FROM extensions WHERE id = ?',
|
|
62
61
|
[pluginId]
|
|
63
|
-
)
|
|
62
|
+
);
|
|
64
63
|
|
|
65
64
|
if (rows.length === 0) {
|
|
66
65
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
4
|
import { ensureDb } from '@/lib/db-init';
|
|
5
|
-
import {
|
|
5
|
+
import { execute } from '@/lib/db';
|
|
6
6
|
import { getPluginDir } from '@/lib/plugin-files';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { promises as fs } from 'fs';
|
|
@@ -19,15 +19,11 @@ function isSafePath(filePath: string): boolean {
|
|
|
19
19
|
/** 验证插件存在 */
|
|
20
20
|
async function verifyPlugin(pluginId: string) {
|
|
21
21
|
await ensureDb();
|
|
22
|
-
const
|
|
23
|
-
const [rows] = await db.execute(
|
|
22
|
+
const [rows] = await execute(
|
|
24
23
|
'SELECT id FROM extensions WHERE id = ?',
|
|
25
24
|
[pluginId]
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return db;
|
|
25
|
+
);
|
|
26
|
+
return (rows as Record<string, unknown>[]).length > 0;
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
// ==================== POST: 上传资源文件 ====================
|
|
@@ -48,8 +44,8 @@ export async function POST(request: NextRequest) {
|
|
|
48
44
|
return NextResponse.json({ error: '非法目标目录' }, { status: 400 });
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
47
|
+
const exists = await verifyPlugin(pluginId);
|
|
48
|
+
if (!exists) {
|
|
53
49
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
54
50
|
}
|
|
55
51
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
4
|
import { ensureDb } from '@/lib/db-init';
|
|
5
|
-
import {
|
|
5
|
+
import { execute } from '@/lib/db';
|
|
6
6
|
import { writePluginResource, getPluginDir } from '@/lib/plugin-files';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { promises as fs } from 'fs';
|
|
@@ -19,19 +19,15 @@ function isSafePath(filePath: string): boolean {
|
|
|
19
19
|
/** 验证插件存在 */
|
|
20
20
|
async function verifyPlugin(pluginId: string) {
|
|
21
21
|
await ensureDb();
|
|
22
|
-
const
|
|
23
|
-
const [rows] = await db.execute(
|
|
22
|
+
const [rows] = await execute(
|
|
24
23
|
'SELECT id FROM extensions WHERE id = ?',
|
|
25
24
|
[pluginId]
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return db;
|
|
25
|
+
);
|
|
26
|
+
return (rows as Record<string, unknown>[]).length > 0;
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
/** 同步 manifest.json 到数据库 */
|
|
34
|
-
async function syncManifestToDb(
|
|
30
|
+
async function syncManifestToDb(pluginId: string, content: string) {
|
|
35
31
|
try {
|
|
36
32
|
const manifest = JSON.parse(content);
|
|
37
33
|
const updates: string[] = [];
|
|
@@ -58,7 +54,7 @@ async function syncManifestToDb(db: ReturnType<typeof getDbPool>, pluginId: stri
|
|
|
58
54
|
|
|
59
55
|
if (updates.length > 0) {
|
|
60
56
|
params.push(pluginId);
|
|
61
|
-
await
|
|
57
|
+
await execute(`UPDATE extensions SET ${updates.join(', ')} WHERE id = ?`, params);
|
|
62
58
|
}
|
|
63
59
|
} catch (e) {
|
|
64
60
|
console.error('同步 manifest 到数据库失败:', e);
|
|
@@ -83,8 +79,8 @@ export async function PUT(request: NextRequest) {
|
|
|
83
79
|
return NextResponse.json({ error: '非法文件路径' }, { status: 400 });
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
82
|
+
const exists = await verifyPlugin(pluginId);
|
|
83
|
+
if (!exists) {
|
|
88
84
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
89
85
|
}
|
|
90
86
|
|
|
@@ -98,7 +94,7 @@ export async function PUT(request: NextRequest) {
|
|
|
98
94
|
await writePluginResource(pluginId, filePath, content);
|
|
99
95
|
|
|
100
96
|
if (filePath === 'manifest.json') {
|
|
101
|
-
await syncManifestToDb(
|
|
97
|
+
await syncManifestToDb(pluginId, content);
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
return NextResponse.json({ success: true, path: filePath });
|
|
@@ -122,8 +118,8 @@ export async function POST(request: NextRequest) {
|
|
|
122
118
|
return NextResponse.json({ error: '非法插件 ID' }, { status: 400 });
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
121
|
+
const exists = await verifyPlugin(pluginId);
|
|
122
|
+
if (!exists) {
|
|
127
123
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
128
124
|
}
|
|
129
125
|
|
|
@@ -180,18 +176,19 @@ export async function DELETE(request: NextRequest) {
|
|
|
180
176
|
return NextResponse.json({ error: '不能删除 resources 目录(资源文件目录,仅支持删除其中的文件)' }, { status: 400 });
|
|
181
177
|
}
|
|
182
178
|
|
|
183
|
-
const
|
|
184
|
-
if (!
|
|
179
|
+
const exists = await verifyPlugin(pluginId);
|
|
180
|
+
if (!exists) {
|
|
185
181
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
186
182
|
}
|
|
187
183
|
|
|
188
184
|
// 从数据库读取 code_path,提取入口文件名,禁止删除入口文件
|
|
189
|
-
const [
|
|
185
|
+
const [codeRows] = await execute(
|
|
190
186
|
'SELECT code_path FROM extensions WHERE id = ?',
|
|
191
187
|
[pluginId]
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
|
|
188
|
+
);
|
|
189
|
+
const codeRowList = codeRows as Record<string, unknown>[];
|
|
190
|
+
if (codeRowList.length > 0 && codeRowList[0].code_path) {
|
|
191
|
+
const entryFile = (codeRowList[0].code_path as string).split('/').pop();
|
|
195
192
|
if (entryFile && filePath === entryFile) {
|
|
196
193
|
return NextResponse.json({ error: `不能删除入口文件 ${entryFile}` }, { status: 400 });
|
|
197
194
|
}
|
|
@@ -236,8 +233,8 @@ export async function PATCH(request: NextRequest) {
|
|
|
236
233
|
return NextResponse.json({ error: '非法文件路径' }, { status: 400 });
|
|
237
234
|
}
|
|
238
235
|
|
|
239
|
-
const
|
|
240
|
-
if (!
|
|
236
|
+
const exists = await verifyPlugin(pluginId);
|
|
237
|
+
if (!exists) {
|
|
241
238
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
242
239
|
}
|
|
243
240
|
|