aicodeswitch 1.10.1 → 2.0.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.
@@ -17,11 +17,13 @@ const cors_1 = __importDefault(require("cors"));
17
17
  const dotenv_1 = __importDefault(require("dotenv"));
18
18
  const fs_1 = __importDefault(require("fs"));
19
19
  const path_1 = __importDefault(require("path"));
20
+ const crypto_1 = require("crypto");
20
21
  const database_1 = require("./database");
21
22
  const proxy_server_1 = require("./proxy-server");
22
23
  const os_1 = __importDefault(require("os"));
23
24
  const auth_1 = require("./auth");
24
25
  const version_check_1 = require("./version-check");
26
+ const utils_1 = require("./utils");
25
27
  const dotenvPath = path_1.default.resolve(os_1.default.homedir(), '.aicodeswitch/aicodeswitch.conf');
26
28
  if (fs_1.default.existsSync(dotenvPath)) {
27
29
  dotenv_1.default.config({ path: dotenvPath });
@@ -283,6 +285,54 @@ const registerRoutes = (dbManager, proxyServer) => {
283
285
  app.put('/api/rules/:id', (req, res) => res.json(dbManager.updateRule(req.params.id, req.body)));
284
286
  app.delete('/api/rules/:id', (req, res) => res.json(dbManager.deleteRule(req.params.id)));
285
287
  app.put('/api/rules/:id/reset-tokens', (req, res) => res.json(dbManager.resetRuleTokenUsage(req.params.id)));
288
+ app.put('/api/rules/:id/reset-requests', (req, res) => res.json(dbManager.resetRuleRequestCount(req.params.id)));
289
+ // 解除规则的黑名单状态
290
+ app.put('/api/rules/:id/clear-blacklist', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
291
+ const { id } = req.params;
292
+ const rule = dbManager.getRule(id);
293
+ if (!rule) {
294
+ res.status(404).json({ error: 'Rule not found' });
295
+ return;
296
+ }
297
+ // 找到该规则所属的路由
298
+ const routes = dbManager.getRoutes();
299
+ const route = routes.find(r => {
300
+ const rules = dbManager.getRules(r.id);
301
+ return rules.some(r => r.id === id);
302
+ });
303
+ if (!route) {
304
+ res.status(404).json({ error: 'Route not found' });
305
+ return;
306
+ }
307
+ try {
308
+ yield dbManager.removeFromBlacklist(rule.targetServiceId, route.id, rule.contentType);
309
+ res.json({ success: true });
310
+ }
311
+ catch (error) {
312
+ console.error('Error clearing blacklist:', error);
313
+ res.status(500).json({ error: 'Failed to clear blacklist' });
314
+ }
315
+ })));
316
+ // 获取规则的黑名单状态
317
+ app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
318
+ const { routeId } = req.params;
319
+ const rules = dbManager.getRules(routeId);
320
+ try {
321
+ const results = yield Promise.all(rules.map((rule) => __awaiter(void 0, void 0, void 0, function* () {
322
+ const blacklistStatus = yield dbManager.getRuleBlacklistStatus(rule.targetServiceId, routeId, rule.contentType);
323
+ return {
324
+ ruleId: rule.id,
325
+ isBlacklisted: blacklistStatus !== null,
326
+ blacklistEntry: blacklistStatus,
327
+ };
328
+ })));
329
+ res.json(results);
330
+ }
331
+ catch (error) {
332
+ console.error('Error getting blacklist status:', error);
333
+ res.status(500).json({ error: 'Failed to get blacklist status' });
334
+ }
335
+ })));
286
336
  app.get('/api/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
287
337
  const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
288
338
  const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
@@ -295,18 +345,6 @@ const registerRoutes = (dbManager, proxyServer) => {
295
345
  yield dbManager.clearLogs();
296
346
  res.json(true);
297
347
  })));
298
- app.get('/api/access-logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
299
- const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
300
- const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
301
- const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
302
- const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
303
- const logs = yield dbManager.getAccessLogs(limit, offset);
304
- res.json(logs);
305
- })));
306
- app.delete('/api/access-logs', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
307
- yield dbManager.clearAccessLogs();
308
- res.json(true);
309
- })));
310
348
  app.get('/api/error-logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
311
349
  const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
312
350
  const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
@@ -323,10 +361,6 @@ const registerRoutes = (dbManager, proxyServer) => {
323
361
  const count = yield dbManager.getLogsCount();
324
362
  res.json({ count });
325
363
  })));
326
- app.get('/api/access-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
327
- const count = yield dbManager.getAccessLogsCount();
328
- res.json({ count });
329
- })));
330
364
  app.get('/api/error-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
331
365
  const count = yield dbManager.getErrorLogsCount();
332
366
  res.json({ count });
@@ -384,6 +418,41 @@ const registerRoutes = (dbManager, proxyServer) => {
384
418
  const stats = yield dbManager.getStatistics(days);
385
419
  res.json(stats);
386
420
  })));
421
+ // Sessions 相关端点
422
+ app.get('/api/sessions', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
423
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
424
+ const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
425
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
426
+ const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
427
+ const sessions = dbManager.getSessions(limit, offset);
428
+ res.json(sessions);
429
+ })));
430
+ app.get('/api/sessions/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
431
+ const count = dbManager.getSessionsCount();
432
+ res.json({ count });
433
+ })));
434
+ app.get('/api/sessions/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
435
+ const session = dbManager.getSession(req.params.id);
436
+ if (!session) {
437
+ res.status(404).json({ error: 'Session not found' });
438
+ return;
439
+ }
440
+ res.json(session);
441
+ })));
442
+ app.get('/api/sessions/:id/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
443
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
444
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
445
+ const logs = yield dbManager.getLogsBySessionId(req.params.id, limit);
446
+ res.json(logs);
447
+ })));
448
+ app.delete('/api/sessions/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
449
+ const result = dbManager.deleteSession(req.params.id);
450
+ res.json(result);
451
+ })));
452
+ app.delete('/api/sessions', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
453
+ dbManager.clearSessions();
454
+ res.json(true);
455
+ })));
387
456
  app.get('/api/docs/recommend-vendors', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
388
457
  const resp = yield fetch('https://unpkg.com/aicodeswitch/docs/vendors-recommand.md');
389
458
  if (!resp.ok) {
@@ -402,24 +471,97 @@ const registerRoutes = (dbManager, proxyServer) => {
402
471
  const text = yield resp.text();
403
472
  res.type('text/plain').send(text);
404
473
  })));
405
- app.use(express_1.default.static(path_1.default.resolve(__dirname, '../ui')));
474
+ // Migration 相关端点
475
+ const getMigrationHashPath = () => path_1.default.join(dataDir, '.migration-hash');
476
+ // 查找 migration.md 文件的路径
477
+ const findMigrationPath = () => {
478
+ // 可能的路径列表
479
+ const possiblePaths = [
480
+ // 开发环境:src/server/main.ts -> public/migration.md
481
+ path_1.default.resolve(__dirname, '../../public/migration.md'),
482
+ // 生产环境:dist/server/main.js -> dist/ui/migration.md
483
+ path_1.default.resolve(__dirname, '../ui/migration.md'),
484
+ ];
485
+ for (const possiblePath of possiblePaths) {
486
+ if (fs_1.default.existsSync(possiblePath)) {
487
+ return possiblePath;
488
+ }
489
+ }
490
+ return null;
491
+ };
492
+ app.get('/api/migration', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
493
+ try {
494
+ // 读取 migration.md 文件
495
+ const migrationPath = findMigrationPath();
496
+ if (!migrationPath) {
497
+ res.json({ shouldShow: false, content: '' });
498
+ return;
499
+ }
500
+ const content = fs_1.default.readFileSync(migrationPath, 'utf-8');
501
+ // 计算当前内容的 hash
502
+ const currentHash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
503
+ // 读取之前保存的 hash
504
+ const hashPath = getMigrationHashPath();
505
+ let savedHash = '';
506
+ if (fs_1.default.existsSync(hashPath)) {
507
+ savedHash = fs_1.default.readFileSync(hashPath, 'utf-8').trim();
508
+ }
509
+ // 如果 hash 不同,需要显示弹窗
510
+ const shouldShow = savedHash !== currentHash;
511
+ res.json({ shouldShow, content: shouldShow ? content : '' });
512
+ }
513
+ catch (error) {
514
+ console.error('Failed to read migration file:', error);
515
+ res.json({ shouldShow: false, content: '' });
516
+ }
517
+ })));
518
+ app.post('/api/migration/ack', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
519
+ try {
520
+ // 读取 migration.md 文件并计算 hash
521
+ const migrationPath = findMigrationPath();
522
+ if (!migrationPath) {
523
+ res.json({ success: false });
524
+ return;
525
+ }
526
+ const content = fs_1.default.readFileSync(migrationPath, 'utf-8');
527
+ const hash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
528
+ // 保存 hash 到文件
529
+ const hashPath = getMigrationHashPath();
530
+ fs_1.default.writeFileSync(hashPath, hash, 'utf-8');
531
+ res.json({ success: true });
532
+ }
533
+ catch (error) {
534
+ console.error('Failed to acknowledge migration:', error);
535
+ res.json({ success: false });
536
+ }
537
+ })));
406
538
  };
407
539
  const start = () => __awaiter(void 0, void 0, void 0, function* () {
408
540
  fs_1.default.mkdirSync(dataDir, { recursive: true });
409
541
  const dbManager = new database_1.DatabaseManager(dataDir);
410
- yield dbManager.initialize();
411
542
  const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
543
+ yield dbManager.initialize();
544
+ // Initialize proxy server and register proxy routes last
545
+ proxyServer.initialize();
412
546
  // Register admin routes first
413
547
  registerRoutes(dbManager, proxyServer);
414
- // Initialize proxy server and register proxy routes last
415
- yield proxyServer.initialize();
416
- const adminServer = app.listen(port, host, () => {
548
+ yield proxyServer.registerProxyRoutes();
549
+ app.use(express_1.default.static(path_1.default.resolve(__dirname, '../ui')));
550
+ const isPortUsable = yield (0, utils_1.checkPortUsable)(port);
551
+ if (!isPortUsable) {
552
+ console.error(`端口 ${port} 已被占用,无法启动服务。请执行 aicos stop 后重启。`);
553
+ process.exit(1);
554
+ }
555
+ const server = app.listen(port, host, () => {
417
556
  console.log(`Admin server running on http://${host}:${port}`);
418
557
  });
419
558
  const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
420
559
  console.log('Shutting down server...');
421
560
  dbManager.close();
422
- adminServer.close(() => process.exit(0));
561
+ server.close(() => {
562
+ console.log('Server stopped.');
563
+ process.exit(0);
564
+ });
423
565
  });
424
566
  process.on('SIGINT', shutdown);
425
567
  process.on('SIGTERM', shutdown);