aicodeswitch 1.0.0 → 1.1.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/CLAUDE.md +182 -0
- package/LICENSE +671 -0
- package/README.md +13 -0
- package/bin/cli.js +35 -3
- package/bin/update.js +251 -0
- package/bin/version.js +97 -0
- package/dist/server/database.js +22 -11
- package/dist/server/proxy-server.js +134 -29
- package/dist/ui/assets/index-BJB9D8Y6.js +285 -0
- package/dist/ui/assets/index-CRLNbjRB.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +29 -3
- package/dist/ui/assets/index-BN77E7-U.js +0 -259
- package/dist/ui/assets/index-CaNSVfpD.css +0 -1
package/README.md
CHANGED
|
@@ -21,6 +21,12 @@ npm install -g aicodeswitch
|
|
|
21
21
|
aicos start
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
**停止服务**
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
aicos stop
|
|
28
|
+
```
|
|
29
|
+
|
|
24
30
|
**进入管理界面**
|
|
25
31
|
|
|
26
32
|
```
|
|
@@ -171,6 +177,13 @@ PORT=4567
|
|
|
171
177
|
|
|
172
178
|
在系统设置页面修改**日志保留天数**配置。
|
|
173
179
|
|
|
180
|
+
## 许可
|
|
181
|
+
|
|
182
|
+
此项目采用双许可证模式:
|
|
183
|
+
|
|
184
|
+
- **开源使用**:项目默认采用 GPL 3.0 许可证,允许个人免费使用、修改和分发,但所有衍生品必须开源。
|
|
185
|
+
- **商业使用**:如果您希望商业化使用而不遵守 GPL 条款(例如闭源销售),请联系我们购买单独的商业许可证。
|
|
186
|
+
|
|
174
187
|
## 技术支持
|
|
175
188
|
|
|
176
189
|
如有问题或建议,请访问项目 [GitHub 仓库](https://github.com/tangshuang/aicodeswitch/issues)提交 Issue。
|
package/bin/cli.js
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
3
7
|
const args = process.argv.slice(2);
|
|
4
8
|
const command = args[0];
|
|
5
9
|
|
|
10
|
+
// 检查是否有更新版本的 current 文件
|
|
11
|
+
const CURRENT_FILE = path.join(os.homedir(), '.aicodeswitch', 'current');
|
|
12
|
+
|
|
13
|
+
let binDir = __dirname;
|
|
14
|
+
let useLocalVersion = true;
|
|
15
|
+
|
|
16
|
+
// 如果存在 current 文件,使用更新版本的脚本
|
|
17
|
+
if (fs.existsSync(CURRENT_FILE)) {
|
|
18
|
+
try {
|
|
19
|
+
const currentPath = fs.readFileSync(CURRENT_FILE, 'utf-8').trim();
|
|
20
|
+
const currentBinDir = path.join(currentPath, 'bin');
|
|
21
|
+
|
|
22
|
+
// 检查新版本的 bin 目录是否存在
|
|
23
|
+
if (fs.existsSync(currentBinDir) && fs.existsSync(path.join(currentBinDir, 'cli.js'))) {
|
|
24
|
+
binDir = currentBinDir;
|
|
25
|
+
useLocalVersion = false;
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// 读取失败,使用本地版本
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
6
32
|
const commands = {
|
|
7
|
-
start: () => require('
|
|
8
|
-
stop: () => require('
|
|
9
|
-
restart: () => require('
|
|
33
|
+
start: () => require(path.join(binDir, 'start')),
|
|
34
|
+
stop: () => require(path.join(binDir, 'stop')),
|
|
35
|
+
restart: () => require(path.join(binDir, 'restart')),
|
|
36
|
+
update: () => require(path.join(binDir, 'update')),
|
|
37
|
+
version: () => require(path.join(binDir, 'version')),
|
|
10
38
|
};
|
|
11
39
|
|
|
12
40
|
if (!command || !commands[command]) {
|
|
@@ -17,11 +45,15 @@ Commands:
|
|
|
17
45
|
start Start the AI Code Switch server
|
|
18
46
|
stop Stop the AI Code Switch server
|
|
19
47
|
restart Restart the AI Code Switch server
|
|
48
|
+
update Update to the latest version and restart
|
|
49
|
+
version Show current version information
|
|
20
50
|
|
|
21
51
|
Example:
|
|
22
52
|
aicos start
|
|
23
53
|
aicos stop
|
|
24
54
|
aicos restart
|
|
55
|
+
aicos update
|
|
56
|
+
aicos version
|
|
25
57
|
`);
|
|
26
58
|
process.exit(1);
|
|
27
59
|
}
|
package/bin/update.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const boxen = require('boxen');
|
|
9
|
+
|
|
10
|
+
const AICOSWITCH_DIR = path.join(os.homedir(), '.aicodeswitch');
|
|
11
|
+
const RELEASES_DIR = path.join(AICOSWITCH_DIR, 'releases');
|
|
12
|
+
const CURRENT_FILE = path.join(AICOSWITCH_DIR, 'current');
|
|
13
|
+
const PACKAGE_NAME = 'aicodeswitch';
|
|
14
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
15
|
+
|
|
16
|
+
// 确保目录存在
|
|
17
|
+
const ensureDir = (dirPath) => {
|
|
18
|
+
if (!fs.existsSync(dirPath)) {
|
|
19
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 获取当前使用的版本(从 current 文件或本地 package.json)
|
|
24
|
+
const getCurrentVersion = () => {
|
|
25
|
+
// 先检查是否有 current 文件(更新的版本)
|
|
26
|
+
if (fs.existsSync(CURRENT_FILE)) {
|
|
27
|
+
try {
|
|
28
|
+
const currentPath = fs.readFileSync(CURRENT_FILE, 'utf-8').trim();
|
|
29
|
+
const currentPackageJson = path.join(currentPath, 'package.json');
|
|
30
|
+
if (fs.existsSync(currentPackageJson)) {
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(currentPackageJson, 'utf-8'));
|
|
32
|
+
return pkg.version;
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// 读取失败,fallback 到本地版本
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 使用本地 package.json
|
|
40
|
+
try {
|
|
41
|
+
const packageJson = path.join(__dirname, '..', 'package.json');
|
|
42
|
+
const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf-8'));
|
|
43
|
+
return pkg.version;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return '0.0.0';
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 比较版本号
|
|
50
|
+
const compareVersions = (v1, v2) => {
|
|
51
|
+
const parts1 = v1.split('.').map(Number);
|
|
52
|
+
const parts2 = v2.split('.').map(Number);
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < 3; i++) {
|
|
55
|
+
if (parts1[i] > parts2[i]) return 1;
|
|
56
|
+
if (parts1[i] < parts2[i]) return -1;
|
|
57
|
+
}
|
|
58
|
+
return 0;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 从 npm registry 获取最新版本
|
|
62
|
+
const getLatestVersion = () => {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const options = {
|
|
65
|
+
hostname: 'registry.npmjs.org',
|
|
66
|
+
path: `/${PACKAGE_NAME}`,
|
|
67
|
+
method: 'GET',
|
|
68
|
+
headers: {
|
|
69
|
+
'User-Agent': 'aicodeswitch'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const req = https.request(options, (res) => {
|
|
74
|
+
let data = '';
|
|
75
|
+
|
|
76
|
+
res.on('data', (chunk) => {
|
|
77
|
+
data += chunk;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
res.on('end', () => {
|
|
81
|
+
try {
|
|
82
|
+
const packageInfo = JSON.parse(data);
|
|
83
|
+
const latestVersion = packageInfo['dist-tags'].latest;
|
|
84
|
+
resolve({
|
|
85
|
+
version: latestVersion,
|
|
86
|
+
tarball: packageInfo.versions[latestVersion].dist.tarball
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
reject(new Error('Failed to parse package info from npm'));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
req.on('error', reject);
|
|
95
|
+
req.end();
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// 使用 npm 安装指定版本到指定目录
|
|
100
|
+
const installPackage = (version, targetDir) => {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
const npmProcess = spawn('npm', [
|
|
103
|
+
'install',
|
|
104
|
+
`${PACKAGE_NAME}@${version}`,
|
|
105
|
+
'--prefix',
|
|
106
|
+
targetDir,
|
|
107
|
+
'--no-save',
|
|
108
|
+
'--no-package-lock',
|
|
109
|
+
'--no-bin-links'
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
let stderr = '';
|
|
113
|
+
|
|
114
|
+
npmProcess.stderr.on('data', (data) => {
|
|
115
|
+
stderr += data.toString();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
npmProcess.on('close', (code) => {
|
|
119
|
+
if (code === 0) {
|
|
120
|
+
// npm install 会把包安装到 targetDir/node_modules/ 目录下
|
|
121
|
+
const packageDir = path.join(targetDir, 'node_modules', PACKAGE_NAME);
|
|
122
|
+
if (fs.existsSync(packageDir)) {
|
|
123
|
+
resolve(packageDir);
|
|
124
|
+
} else {
|
|
125
|
+
reject(new Error('Package installation directory not found'));
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
reject(new Error(`npm install failed: ${stderr}`));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
npmProcess.on('error', reject);
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// 更新 current 文件
|
|
137
|
+
const updateCurrentFile = (versionPath) => {
|
|
138
|
+
fs.writeFileSync(CURRENT_FILE, versionPath);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// 执行 restart
|
|
142
|
+
const restart = () => {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const restartScript = path.join(__dirname, 'restart.js');
|
|
145
|
+
|
|
146
|
+
const restartProcess = spawn('node', [restartScript], {
|
|
147
|
+
stdio: 'inherit'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
restartProcess.on('close', (code) => {
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
resolve();
|
|
153
|
+
} else {
|
|
154
|
+
reject(new Error(`Restart failed with exit code ${code}`));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
restartProcess.on('error', reject);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// 主更新逻辑
|
|
163
|
+
const update = async () => {
|
|
164
|
+
console.log('\n');
|
|
165
|
+
|
|
166
|
+
const currentVersion = getCurrentVersion();
|
|
167
|
+
const spinner = ora({
|
|
168
|
+
text: chalk.cyan('Checking for updates...'),
|
|
169
|
+
color: 'cyan'
|
|
170
|
+
}).start();
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// 获取最新版本信息
|
|
174
|
+
const latestInfo = await getLatestVersion();
|
|
175
|
+
const latestVersion = latestInfo.version;
|
|
176
|
+
|
|
177
|
+
spinner.succeed(chalk.green(`Latest version: ${chalk.bold(latestVersion)}`));
|
|
178
|
+
|
|
179
|
+
// 检查是否需要更新
|
|
180
|
+
const comparison = compareVersions(latestVersion, currentVersion);
|
|
181
|
+
|
|
182
|
+
if (comparison <= 0) {
|
|
183
|
+
console.log(chalk.yellow(`\n✓ You are already on the latest version (${chalk.bold(currentVersion)})\n`));
|
|
184
|
+
process.exit(0);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(chalk.cyan(`\n📦 Update available: ${chalk.bold(currentVersion)} → ${chalk.bold(latestVersion)}\n`));
|
|
189
|
+
|
|
190
|
+
// 确保目录存在
|
|
191
|
+
ensureDir(RELEASES_DIR);
|
|
192
|
+
|
|
193
|
+
// 安装新版本
|
|
194
|
+
const installSpinner = ora({
|
|
195
|
+
text: chalk.cyan('Downloading and installing from npm...'),
|
|
196
|
+
color: 'cyan'
|
|
197
|
+
}).start();
|
|
198
|
+
|
|
199
|
+
const versionDir = path.join(RELEASES_DIR, latestVersion);
|
|
200
|
+
ensureDir(versionDir);
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const packageDir = await installPackage(latestVersion, versionDir);
|
|
204
|
+
installSpinner.succeed(chalk.green('Package installed'));
|
|
205
|
+
} catch (err) {
|
|
206
|
+
installSpinner.fail(chalk.red('Installation failed'));
|
|
207
|
+
console.log(chalk.red(`Error: ${err.message}\n`));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 实际的包在 node_modules/aicodeswitch 目录下
|
|
213
|
+
const actualPackageDir = path.join(versionDir, 'node_modules', PACKAGE_NAME);
|
|
214
|
+
updateCurrentFile(actualPackageDir);
|
|
215
|
+
|
|
216
|
+
// 显示更新成功信息
|
|
217
|
+
console.log(boxen(
|
|
218
|
+
chalk.green.bold('✨ Update Successful!\n\n') +
|
|
219
|
+
chalk.white('Version: ') + chalk.cyan.bold(latestVersion) + '\n' +
|
|
220
|
+
chalk.white('Location: ') + chalk.gray(actualPackageDir) + '\n\n' +
|
|
221
|
+
chalk.gray('Restarting server with the new version...'),
|
|
222
|
+
{
|
|
223
|
+
padding: 1,
|
|
224
|
+
margin: 1,
|
|
225
|
+
borderStyle: 'double',
|
|
226
|
+
borderColor: 'green'
|
|
227
|
+
}
|
|
228
|
+
));
|
|
229
|
+
|
|
230
|
+
// 重启服务器
|
|
231
|
+
try {
|
|
232
|
+
await restart();
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.log(chalk.yellow(`\n⚠️ Update completed, but restart failed: ${err.message}`));
|
|
235
|
+
console.log(chalk.cyan('Please manually run: ') + chalk.yellow('aicos restart\n'));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
process.exit(0);
|
|
241
|
+
|
|
242
|
+
} catch (err) {
|
|
243
|
+
spinner.fail(chalk.red('Update check failed'));
|
|
244
|
+
console.log(chalk.red(`Error: ${err.message}\n`));
|
|
245
|
+
console.log(chalk.gray('You can check for updates manually at:\n'));
|
|
246
|
+
console.log(chalk.cyan(' https://www.npmjs.com/package/aicodeswitch\n'));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
module.exports = update();
|
package/bin/version.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const boxen = require('boxen');
|
|
6
|
+
|
|
7
|
+
const AICOSWITCH_DIR = path.join(os.homedir(), '.aicodeswitch');
|
|
8
|
+
const CURRENT_FILE = path.join(AICOSWITCH_DIR, 'current');
|
|
9
|
+
|
|
10
|
+
const getVersionInfo = () => {
|
|
11
|
+
// 先检查是否有 current 文件(更新的版本)
|
|
12
|
+
if (fs.existsSync(CURRENT_FILE)) {
|
|
13
|
+
try {
|
|
14
|
+
const currentPath = fs.readFileSync(CURRENT_FILE, 'utf-8').trim();
|
|
15
|
+
const currentPackageJson = path.join(currentPath, 'package.json');
|
|
16
|
+
|
|
17
|
+
if (fs.existsSync(currentPackageJson)) {
|
|
18
|
+
const pkg = JSON.parse(fs.readFileSync(currentPackageJson, 'utf-8'));
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
version: pkg.version,
|
|
22
|
+
source: 'npm',
|
|
23
|
+
path: currentPath,
|
|
24
|
+
isUpdated: true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// 读取失败,fallback 到本地版本
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 使用本地 package.json
|
|
33
|
+
try {
|
|
34
|
+
const packageJson = path.join(__dirname, '..', 'package.json');
|
|
35
|
+
const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf-8'));
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
version: pkg.version,
|
|
39
|
+
source: 'local',
|
|
40
|
+
path: path.dirname(packageJson),
|
|
41
|
+
isUpdated: false
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return {
|
|
45
|
+
version: 'unknown',
|
|
46
|
+
source: 'unknown',
|
|
47
|
+
path: 'unknown',
|
|
48
|
+
isUpdated: false
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const version = () => {
|
|
54
|
+
const info = getVersionInfo();
|
|
55
|
+
|
|
56
|
+
console.log('\n');
|
|
57
|
+
|
|
58
|
+
if (info.isUpdated) {
|
|
59
|
+
console.log(boxen(
|
|
60
|
+
chalk.green.bold('AI Code Switch\n\n') +
|
|
61
|
+
chalk.white('Version: ') + chalk.cyan.bold(info.version) + '\n' +
|
|
62
|
+
chalk.white('Source: ') + chalk.yellow.bold('npm (updated)') + '\n' +
|
|
63
|
+
chalk.white('Location: ') + chalk.gray(info.path),
|
|
64
|
+
{
|
|
65
|
+
padding: 1,
|
|
66
|
+
margin: 1,
|
|
67
|
+
borderStyle: 'double',
|
|
68
|
+
borderColor: 'green'
|
|
69
|
+
}
|
|
70
|
+
));
|
|
71
|
+
|
|
72
|
+
console.log(chalk.cyan('💡 Tips:\n'));
|
|
73
|
+
console.log(chalk.white(' • Check for updates: ') + chalk.yellow('aicos update'));
|
|
74
|
+
console.log(chalk.white(' • Revert to local: ') + chalk.gray('rm ~/.aicodeswitch/current\n'));
|
|
75
|
+
} else {
|
|
76
|
+
console.log(boxen(
|
|
77
|
+
chalk.cyan.bold('AI Code Switch\n\n') +
|
|
78
|
+
chalk.white('Version: ') + chalk.cyan.bold(info.version) + '\n' +
|
|
79
|
+
chalk.white('Source: ') + chalk.yellow.bold('local development') + '\n' +
|
|
80
|
+
chalk.white('Location: ') + chalk.gray(info.path),
|
|
81
|
+
{
|
|
82
|
+
padding: 1,
|
|
83
|
+
margin: 1,
|
|
84
|
+
borderStyle: 'double',
|
|
85
|
+
borderColor: 'cyan'
|
|
86
|
+
}
|
|
87
|
+
));
|
|
88
|
+
|
|
89
|
+
console.log(chalk.cyan('💡 Tips:\n'));
|
|
90
|
+
console.log(chalk.white(' • Check for updates: ') + chalk.yellow('aicos update'));
|
|
91
|
+
console.log(chalk.white(' • Update to latest: ') + chalk.yellow('aicos update\n'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
process.exit(0);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports = version();
|
package/dist/server/database.js
CHANGED
|
@@ -99,14 +99,15 @@ class DatabaseManager {
|
|
|
99
99
|
CREATE TABLE IF NOT EXISTS rules (
|
|
100
100
|
id TEXT PRIMARY KEY,
|
|
101
101
|
route_id TEXT NOT NULL,
|
|
102
|
-
content_type TEXT NOT NULL CHECK(content_type IN ('default', 'background', 'thinking', 'long-context', 'image-understanding')),
|
|
102
|
+
content_type TEXT NOT NULL CHECK(content_type IN ('default', 'background', 'thinking', 'long-context', 'image-understanding', 'model-mapping')),
|
|
103
103
|
target_service_id TEXT NOT NULL,
|
|
104
104
|
target_model TEXT,
|
|
105
|
+
replaced_model TEXT,
|
|
106
|
+
sort_order INTEGER DEFAULT 0,
|
|
105
107
|
created_at INTEGER NOT NULL,
|
|
106
108
|
updated_at INTEGER NOT NULL,
|
|
107
109
|
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
|
|
108
|
-
FOREIGN KEY (target_service_id) REFERENCES api_services(id) ON DELETE CASCADE
|
|
109
|
-
UNIQUE(route_id, content_type)
|
|
110
|
+
FOREIGN KEY (target_service_id) REFERENCES api_services(id) ON DELETE CASCADE
|
|
110
111
|
);
|
|
111
112
|
|
|
112
113
|
CREATE TABLE IF NOT EXISTS config (
|
|
@@ -246,8 +247,8 @@ class DatabaseManager {
|
|
|
246
247
|
// Rule operations
|
|
247
248
|
getRules(routeId) {
|
|
248
249
|
const query = routeId
|
|
249
|
-
? 'SELECT * FROM rules WHERE route_id = ? ORDER BY created_at DESC'
|
|
250
|
-
: 'SELECT * FROM rules ORDER BY created_at DESC';
|
|
250
|
+
? 'SELECT * FROM rules WHERE route_id = ? ORDER BY sort_order DESC, created_at DESC'
|
|
251
|
+
: 'SELECT * FROM rules ORDER BY sort_order DESC, created_at DESC';
|
|
251
252
|
const stmt = routeId ? this.db.prepare(query).bind(routeId) : this.db.prepare(query);
|
|
252
253
|
const rows = stmt.all();
|
|
253
254
|
return rows.map((row) => ({
|
|
@@ -256,6 +257,8 @@ class DatabaseManager {
|
|
|
256
257
|
contentType: row.content_type,
|
|
257
258
|
targetServiceId: row.target_service_id,
|
|
258
259
|
targetModel: row.target_model,
|
|
260
|
+
replacedModel: row.replaced_model,
|
|
261
|
+
sortOrder: row.sort_order,
|
|
259
262
|
createdAt: row.created_at,
|
|
260
263
|
updatedAt: row.updated_at,
|
|
261
264
|
}));
|
|
@@ -264,15 +267,15 @@ class DatabaseManager {
|
|
|
264
267
|
const id = crypto_1.default.randomUUID();
|
|
265
268
|
const now = Date.now();
|
|
266
269
|
this.db
|
|
267
|
-
.prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
268
|
-
.run(id, route.routeId, route.contentType, route.targetServiceId, route.targetModel || null, now, now);
|
|
270
|
+
.prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
271
|
+
.run(id, route.routeId, route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, now, now);
|
|
269
272
|
return Object.assign(Object.assign({}, route), { id, createdAt: now, updatedAt: now });
|
|
270
273
|
}
|
|
271
274
|
updateRule(id, route) {
|
|
272
275
|
const now = Date.now();
|
|
273
276
|
const result = this.db
|
|
274
|
-
.prepare('UPDATE rules SET content_type = ?, target_service_id = ?, target_model = ?, updated_at = ? WHERE id = ?')
|
|
275
|
-
.run(route.contentType, route.targetServiceId, route.targetModel || null, now, id);
|
|
277
|
+
.prepare('UPDATE rules SET content_type = ?, target_service_id = ?, target_model = ?, replaced_model = ?, sort_order = ?, updated_at = ? WHERE id = ?')
|
|
278
|
+
.run(route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, now, id);
|
|
276
279
|
return result.changes > 0;
|
|
277
280
|
}
|
|
278
281
|
deleteRule(id) {
|
|
@@ -324,6 +327,14 @@ class DatabaseManager {
|
|
|
324
327
|
return __awaiter(this, void 0, void 0, function* () {
|
|
325
328
|
const id = crypto_1.default.randomUUID();
|
|
326
329
|
yield this.accessLogDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
|
|
330
|
+
return id;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
updateAccessLog(id, data) {
|
|
334
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
335
|
+
const log = yield this.accessLogDb.get(id);
|
|
336
|
+
const updatedLog = Object.assign(Object.assign({}, JSON.parse(log)), data);
|
|
337
|
+
yield this.accessLogDb.put(id, JSON.stringify(updatedLog));
|
|
327
338
|
});
|
|
328
339
|
}
|
|
329
340
|
getAccessLogs() {
|
|
@@ -459,8 +470,8 @@ class DatabaseManager {
|
|
|
459
470
|
// Import rules
|
|
460
471
|
for (const rule of importData.rules) {
|
|
461
472
|
this.db
|
|
462
|
-
.prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
|
|
463
|
-
.run(rule.id, rule.routeId, rule.contentType || 'default', rule.targetServiceId, rule.createdAt, rule.updatedAt);
|
|
473
|
+
.prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
474
|
+
.run(rule.id, rule.routeId, rule.contentType || 'default', rule.targetServiceId, rule.targetModel || null, rule.replacedModel || null, rule.sortOrder || 0, rule.createdAt, rule.updatedAt);
|
|
464
475
|
}
|
|
465
476
|
// Update config
|
|
466
477
|
this.updateConfig(importData.config);
|