blockmine 1.22.0 → 1.23.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.
Files changed (108) hide show
  1. package/.claude/agents/code-architect.md +34 -0
  2. package/.claude/agents/code-explorer.md +51 -0
  3. package/.claude/agents/code-reviewer.md +46 -0
  4. package/.claude/commands/feature-dev.md +125 -0
  5. package/.claude/settings.json +5 -1
  6. package/.claude/settings.local.json +12 -1
  7. package/.claude/skills/frontend-design/SKILL.md +42 -0
  8. package/CHANGELOG.md +32 -1
  9. package/README.md +302 -152
  10. package/backend/package-lock.json +681 -9
  11. package/backend/package.json +8 -0
  12. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
  13. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
  14. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
  15. package/backend/prisma/schema.prisma +70 -1
  16. package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
  17. package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
  18. package/backend/src/api/middleware/auth.js +27 -0
  19. package/backend/src/api/middleware/botAccess.js +7 -3
  20. package/backend/src/api/middleware/panelApiAuth.js +135 -0
  21. package/backend/src/api/routes/aiAssistant.js +995 -0
  22. package/backend/src/api/routes/auth.js +90 -54
  23. package/backend/src/api/routes/botCommands.js +107 -0
  24. package/backend/src/api/routes/botGroups.js +165 -0
  25. package/backend/src/api/routes/botHistory.js +108 -0
  26. package/backend/src/api/routes/botPermissions.js +99 -0
  27. package/backend/src/api/routes/botStatus.js +36 -0
  28. package/backend/src/api/routes/botUsers.js +162 -0
  29. package/backend/src/api/routes/bots.js +108 -59
  30. package/backend/src/api/routes/eventGraphs.js +4 -1
  31. package/backend/src/api/routes/logs.js +13 -3
  32. package/backend/src/api/routes/panel.js +3 -3
  33. package/backend/src/api/routes/panelApiKeys.js +179 -0
  34. package/backend/src/api/routes/pluginIde.js +1715 -135
  35. package/backend/src/api/routes/plugins.js +170 -13
  36. package/backend/src/api/routes/proxies.js +130 -0
  37. package/backend/src/api/routes/search.js +4 -0
  38. package/backend/src/api/routes/servers.js +20 -3
  39. package/backend/src/api/routes/settings.js +5 -0
  40. package/backend/src/api/routes/system.js +3 -3
  41. package/backend/src/api/routes/traces.js +131 -0
  42. package/backend/src/config/debug.config.js +36 -0
  43. package/backend/src/core/BotHistoryStore.js +180 -0
  44. package/backend/src/core/BotManager.js +14 -4
  45. package/backend/src/core/BotProcess.js +1517 -1092
  46. package/backend/src/core/EventGraphManager.js +194 -280
  47. package/backend/src/core/GraphExecutionEngine.js +1004 -321
  48. package/backend/src/core/MessageQueue.js +12 -6
  49. package/backend/src/core/PluginLoader.js +99 -5
  50. package/backend/src/core/PluginManager.js +74 -13
  51. package/backend/src/core/TaskScheduler.js +1 -1
  52. package/backend/src/core/commands/whois.js +1 -1
  53. package/backend/src/core/node-registries/actions.js +72 -2
  54. package/backend/src/core/node-registries/arrays.js +18 -0
  55. package/backend/src/core/node-registries/data.js +1 -1
  56. package/backend/src/core/node-registries/events.js +14 -0
  57. package/backend/src/core/node-registries/logic.js +17 -0
  58. package/backend/src/core/node-registries/strings.js +34 -0
  59. package/backend/src/core/node-registries/type.js +25 -0
  60. package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
  61. package/backend/src/core/nodes/actions/create_command.js +189 -0
  62. package/backend/src/core/nodes/actions/delete_command.js +92 -0
  63. package/backend/src/core/nodes/actions/http_request.js +23 -4
  64. package/backend/src/core/nodes/actions/send_message.js +2 -12
  65. package/backend/src/core/nodes/actions/update_command.js +133 -0
  66. package/backend/src/core/nodes/arrays/join.js +28 -0
  67. package/backend/src/core/nodes/data/cast.js +2 -1
  68. package/backend/src/core/nodes/data/string_literal.js +2 -13
  69. package/backend/src/core/nodes/logic/not.js +22 -0
  70. package/backend/src/core/nodes/strings/starts_with.js +1 -1
  71. package/backend/src/core/nodes/strings/to_lower.js +22 -0
  72. package/backend/src/core/nodes/strings/to_upper.js +22 -0
  73. package/backend/src/core/nodes/type/to_string.js +32 -0
  74. package/backend/src/core/services/BotLifecycleService.js +835 -596
  75. package/backend/src/core/services/CommandExecutionService.js +430 -351
  76. package/backend/src/core/services/DebugSessionManager.js +347 -0
  77. package/backend/src/core/services/GraphCollaborationManager.js +501 -0
  78. package/backend/src/core/services/MinecraftBotManager.js +259 -0
  79. package/backend/src/core/services/MinecraftViewerService.js +216 -0
  80. package/backend/src/core/services/TraceCollectorService.js +545 -0
  81. package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
  82. package/backend/src/core/system/Transport.js +0 -4
  83. package/backend/src/core/validation/nodeSchemas.js +6 -6
  84. package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
  85. package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
  86. package/backend/src/real-time/botApi/utils.js +11 -0
  87. package/backend/src/real-time/panelNamespace.js +387 -0
  88. package/backend/src/real-time/presence.js +7 -2
  89. package/backend/src/real-time/socketHandler.js +395 -4
  90. package/backend/src/server.js +18 -0
  91. package/frontend/dist/assets/index-DqzDkFsP.js +11210 -0
  92. package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
  93. package/frontend/dist/index.html +2 -2
  94. package/frontend/package-lock.json +9437 -0
  95. package/frontend/package.json +8 -0
  96. package/package.json +2 -2
  97. package/screen/console.png +0 -0
  98. package/screen/dashboard.png +0 -0
  99. package/screen/graph_collabe.png +0 -0
  100. package/screen/graph_live_debug.png +0 -0
  101. package/screen/management_command.png +0 -0
  102. package/screen/node_debug_trace.png +0 -0
  103. package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
  104. package/screen/websocket.png +0 -0
  105. package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
  106. package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
  107. package/frontend/dist/assets/index-CfTo92bP.css +0 -1
  108. package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
@@ -5,6 +5,7 @@ const crypto = require('crypto');
5
5
  const { PrismaClient } = require('@prisma/client');
6
6
  const config = require('../../config');
7
7
  const { authenticate, authorize } = require('../middleware/auth');
8
+ const { authenticatePanelApiKey } = require('../middleware/panelApiAuth');
8
9
  const path = require('path');
9
10
  const os = require('os');
10
11
 
@@ -16,6 +17,66 @@ const JWT_EXPIRES_IN = '7d';
16
17
 
17
18
  const activeResetTokens = new Map();
18
19
 
20
+ const ALL_PERMISSIONS = [
21
+ { id: '*', label: 'Все права (Администратор)' },
22
+ { id: 'bot:list', label: 'Просмотр ботов' },
23
+ { id: 'bot:create', label: 'Создание ботов' },
24
+ { id: 'bot:update', label: 'Редактирование ботов' },
25
+ { id: 'bot:delete', label: 'Удаление ботов' },
26
+ { id: 'bot:start_stop', label: 'Запуск/остановка ботов' },
27
+ { id: 'bot:interact', label: 'Взаимодействие с ботом (консоль)' },
28
+ { id: 'bot:export', label: 'Экспорт ботов' },
29
+ { id: 'bot:import', label: 'Импорт ботов' },
30
+ { id: 'bot:history:view', label: 'Просмотр истории чата и команд' },
31
+ { id: 'bot:history:manage', label: 'Управление историей (очистка)' },
32
+ { id: 'management:view', label: 'Просмотр вкладки "Управление" у бота' },
33
+ { id: 'management:edit', label: 'Редактирование на вкладке "Управление" у бота' },
34
+ { id: 'plugin:list', label: 'Просмотр плагинов' },
35
+ { id: 'plugin:install', label: 'Установка плагинов' },
36
+ { id: 'plugin:delete', label: 'Удаление плагинов' },
37
+ { id: 'plugin:update', label: 'Обновление плагинов' },
38
+ { id: 'plugin:settings:view', label: 'Просмотр настроек плагинов' },
39
+ { id: 'plugin:settings:edit', label: 'Редактирование настроек плагинов' },
40
+ { id: 'plugin:browse', label: 'Просмотр каталога плагинов' },
41
+ { id: 'plugin:develop', label: 'Разработка и редактирование плагинов (IDE)' },
42
+ { id: 'server:list', label: 'Просмотр серверов' },
43
+ { id: 'server:create', label: 'Создание серверов' },
44
+ { id: 'server:delete', label: 'Удаление серверов' },
45
+ { id: 'proxy:list', label: 'Просмотр прокси' },
46
+ { id: 'proxy:create', label: 'Создание и редактирование прокси' },
47
+ { id: 'proxy:delete', label: 'Удаление прокси' },
48
+ { id: 'task:list', label: 'Просмотр задач' },
49
+ { id: 'task:create', label: 'Создание задач' },
50
+ { id: 'task:edit', label: 'Редактирование задач' },
51
+ { id: 'task:delete', label: 'Удаление задач' },
52
+ { id: 'panel:user:list', label: 'Просмотр пользователей панели' },
53
+ { id: 'panel:user:create', label: 'Создание пользователей панели' },
54
+ { id: 'panel:user:edit', label: 'Редактирование пользователей панели' },
55
+ { id: 'panel:user:delete', label: 'Удаление пользователей панели' },
56
+ { id: 'panel:role:list', label: 'Просмотр ролей панели' },
57
+ { id: 'panel:role:create', label: 'Создание ролей панели' },
58
+ { id: 'panel:role:edit', label: 'Редактирование ролей панели' },
59
+ { id: 'panel:role:delete', label: 'Удаление ролей панели' },
60
+ { id: 'panel:settings:view', label: 'Просмотр глобальных настроек' },
61
+ { id: 'panel:settings:edit', label: 'Редактирование глобальных настроек' },
62
+ { id: 'graph:read', label: 'Просмотр магазина графов' },
63
+ { id: 'graph:download', label: 'Скачивание графов из магазина' },
64
+ { id: 'graph:like', label: 'Лайки графов в магазине' },
65
+ { id: 'graph:publish', label: 'Публикация графов в магазин' },
66
+ ];
67
+
68
+ const VIEWER_PERMISSIONS = [
69
+ 'bot:list',
70
+ 'bot:history:view',
71
+ 'plugin:list',
72
+ 'plugin:settings:view',
73
+ 'management:view',
74
+ 'server:list',
75
+ 'proxy:list',
76
+ 'task:list',
77
+ 'graph:read',
78
+ ];
79
+
19
80
  function ownerOnly(req, res, next) {
20
81
  if (req.user && req.user.userId === 1) return next();
21
82
  return res.status(403).json({ error: 'Только владелец может изменять права пользователей и роли.' });
@@ -271,7 +332,12 @@ router.post('/login', async (req, res) => {
271
332
  return res.status(401).json({ error: 'Неверные учетные данные' });
272
333
  }
273
334
 
274
- const permissions = JSON.parse(user.role.permissions || '[]');
335
+ let permissions = JSON.parse(user.role.permissions || '[]');
336
+
337
+ // Владелец (первый пользователь) всегда получает все актуальные права
338
+ if (user.id === 1) {
339
+ permissions = ALL_PERMISSIONS.map(p => p.id).filter(id => id !== '*');
340
+ }
275
341
 
276
342
  const payload = {
277
343
  userId: user.id,
@@ -297,6 +363,28 @@ router.post('/login', async (req, res) => {
297
363
  });
298
364
 
299
365
 
366
+ /**
367
+ * @route GET /api/auth/validate
368
+ * @desc Validate Panel API key
369
+ * @access Public (requires API key)
370
+ */
371
+ router.get('/validate', authenticatePanelApiKey, async (req, res) => {
372
+ res.json({
373
+ valid: true,
374
+ user: {
375
+ id: req.user.id,
376
+ username: req.user.username,
377
+ role: req.user.roleName,
378
+ permissions: req.user.permissions
379
+ },
380
+ apiKey: {
381
+ id: req.apiKey.id,
382
+ name: req.apiKey.name,
383
+ prefix: req.apiKey.prefix
384
+ }
385
+ });
386
+ });
387
+
300
388
  /**
301
389
  * @route GET /api/auth/me
302
390
  * @desc Получить данные текущего пользователя по токену
@@ -312,7 +400,7 @@ router.get('/me', authenticate, async (req, res) => {
312
400
  if (!user) {
313
401
  return res.status(404).json({ error: "Пользователь не найден." });
314
402
  }
315
-
403
+
316
404
  res.json({
317
405
  id: user.id,
318
406
  username: user.username,
@@ -401,58 +489,6 @@ router.get('/roles', authenticate, authorize('panel:role:list'), async (req, res
401
489
  }
402
490
  });
403
491
 
404
- const ALL_PERMISSIONS = [
405
- { id: '*', label: 'Все права (Администратор)' },
406
- { id: 'bot:list', label: 'Просмотр ботов' },
407
- { id: 'bot:create', label: 'Создание ботов' },
408
- { id: 'bot:update', label: 'Редактирование ботов' },
409
- { id: 'bot:delete', label: 'Удаление ботов' },
410
- { id: 'bot:start_stop', label: 'Запуск/остановка ботов' },
411
- { id: 'bot:interact', label: 'Взаимодействие с ботом (консоль)' },
412
- { id: 'bot:export', label: 'Экспорт ботов' },
413
- { id: 'bot:import', label: 'Импорт ботов' },
414
- { id: 'management:view', label: 'Просмотр вкладки "Управление" у бота' },
415
- { id: 'management:edit', label: 'Редактирование на вкладке "Управление" у бота' },
416
- { id: 'plugin:list', label: 'Просмотр плагинов' },
417
- { id: 'plugin:install', label: 'Установка плагинов' },
418
- { id: 'plugin:delete', label: 'Удаление плагинов' },
419
- { id: 'plugin:update', label: 'Обновление плагинов' },
420
- { id: 'plugin:settings:view', label: 'Просмотр настроек плагинов' },
421
- { id: 'plugin:settings:edit', label: 'Редактирование настроек плагинов' },
422
- { id: 'plugin:browse', label: 'Просмотр каталога плагинов' },
423
- { id: 'plugin:develop', label: 'Разработка и редактирование плагинов (IDE)' },
424
- { id: 'server:list', label: 'Просмотр серверов' },
425
- { id: 'server:create', label: 'Создание серверов' },
426
- { id: 'server:delete', label: 'Удаление серверов' },
427
- { id: 'task:list', label: 'Просмотр задач' },
428
- { id: 'task:create', label: 'Создание задач' },
429
- { id: 'task:edit', label: 'Редактирование задач' },
430
- { id: 'task:delete', label: 'Удаление задач' },
431
- { id: 'panel:user:list', label: 'Просмотр пользователей панели' },
432
- { id: 'panel:user:create', label: 'Создание пользователей панели' },
433
- { id: 'panel:user:edit', label: 'Редактирование пользователей панели' },
434
- { id: 'panel:user:delete', label: 'Удаление пользователей панели' },
435
- { id: 'panel:role:list', label: 'Просмотр ролей панели' },
436
- { id: 'panel:role:create', label: 'Создание ролей панели' },
437
- { id: 'panel:role:edit', label: 'Редактирование ролей панели' },
438
- { id: 'panel:role:delete', label: 'Удаление ролей панели' },
439
- { id: 'panel:settings:view', label: 'Просмотр глобальных настроек' },
440
- { id: 'panel:settings:edit', label: 'Редактирование глобальных настроек' },
441
- { id: 'graph:read', label: 'Просмотр магазина графов' },
442
- { id: 'graph:download', label: 'Скачивание графов из магазина' },
443
- { id: 'graph:like', label: 'Лайки графов в магазине' },
444
- { id: 'graph:publish', label: 'Публикация графов в магазин' },
445
- ];
446
-
447
- const VIEWER_PERMISSIONS = [
448
- 'bot:list',
449
- 'plugin:list',
450
- 'plugin:settings:view',
451
- 'management:view',
452
- 'server:list',
453
- 'task:list',
454
- 'graph:read',
455
- ];
456
492
 
457
493
  /**
458
494
  * @route GET /api/auth/permissions
@@ -0,0 +1,107 @@
1
+ const express = require('express');
2
+ const prisma = require('../../lib/prisma');
3
+ const { authenticateUniversal, authorize } = require('../middleware/auth');
4
+
5
+ const router = express.Router();
6
+
7
+ router.get('/:botId/commands', authenticateUniversal, authorize('management:view'), async (req, res) => {
8
+ try {
9
+ const botId = parseInt(req.params.botId, 10);
10
+ const { page = 1, pageSize = 100, search } = req.query;
11
+
12
+ const skip = (parseInt(page) - 1) * parseInt(pageSize);
13
+ const take = parseInt(pageSize);
14
+
15
+ const where = { botId };
16
+ if (search) {
17
+ where.name = { contains: search };
18
+ }
19
+
20
+ const [commands, total] = await Promise.all([
21
+ prisma.command.findMany({
22
+ where,
23
+ include: {
24
+ permission: true,
25
+ pluginOwner: { select: { id: true, name: true, version: true } }
26
+ },
27
+ orderBy: { name: 'asc' },
28
+ skip,
29
+ take
30
+ }),
31
+ prisma.command.count({ where })
32
+ ]);
33
+
34
+ res.json({
35
+ items: commands,
36
+ total,
37
+ page: parseInt(page),
38
+ pageSize: take
39
+ });
40
+ } catch (error) {
41
+ console.error('[API Error] GET /bots/:botId/commands:', error);
42
+ res.status(500).json({ error: 'Не удалось получить список команд' });
43
+ }
44
+ });
45
+
46
+ router.get('/:botId/commands/:commandId', authenticateUniversal, authorize('management:view'), async (req, res) => {
47
+ try {
48
+ const botId = parseInt(req.params.botId, 10);
49
+ const commandId = parseInt(req.params.commandId, 10);
50
+
51
+ const command = await prisma.command.findFirst({
52
+ where: { id: commandId, botId },
53
+ include: {
54
+ permission: true,
55
+ pluginOwner: { select: { id: true, name: true, version: true } }
56
+ }
57
+ });
58
+
59
+ if (!command) {
60
+ return res.status(404).json({ error: 'Команда не найдена' });
61
+ }
62
+
63
+ res.json(command);
64
+ } catch (error) {
65
+ console.error('[API Error] GET /bots/:botId/commands/:commandId:', error);
66
+ res.status(500).json({ error: 'Не удалось получить команду' });
67
+ }
68
+ });
69
+
70
+ router.patch('/:botId/commands/:commandId', authenticateUniversal, authorize('management:edit'), async (req, res) => {
71
+ try {
72
+ const botId = parseInt(req.params.botId, 10);
73
+ const commandId = parseInt(req.params.commandId, 10);
74
+ const { isEnabled, cooldown, aliases, permissionId, allowedChatTypes } = req.body;
75
+
76
+ const command = await prisma.command.findFirst({
77
+ where: { id: commandId, botId }
78
+ });
79
+
80
+ if (!command) {
81
+ return res.status(404).json({ error: 'Команда не найдена' });
82
+ }
83
+
84
+ const updateData = {};
85
+ if (typeof isEnabled === 'boolean') updateData.isEnabled = isEnabled;
86
+ if (typeof cooldown === 'number') updateData.cooldown = cooldown;
87
+ if (aliases !== undefined) updateData.aliases = JSON.stringify(aliases);
88
+ if (permissionId !== undefined) updateData.permissionId = permissionId;
89
+ if (allowedChatTypes !== undefined) updateData.allowedChatTypes = JSON.stringify(allowedChatTypes);
90
+
91
+ const updated = await prisma.command.update({
92
+ where: { id: commandId },
93
+ data: updateData,
94
+ include: {
95
+ permission: true,
96
+ pluginOwner: { select: { id: true, name: true, version: true } }
97
+ }
98
+ });
99
+
100
+ res.json(updated);
101
+ } catch (error) {
102
+ console.error('[API Error] PATCH /bots/:botId/commands/:commandId:', error);
103
+ res.status(500).json({ error: 'Не удалось обновить команду' });
104
+ }
105
+ });
106
+
107
+ module.exports = router;
@@ -0,0 +1,165 @@
1
+ const express = require('express');
2
+ const prisma = require('../../lib/prisma');
3
+ const { authenticateUniversal, authorize } = require('../middleware/auth');
4
+ const { botManager } = require('../../core/services');
5
+ const UserService = require('../../core/UserService');
6
+
7
+ const router = express.Router();
8
+
9
+ router.get('/:botId/groups', authenticateUniversal, authorize('management:view'), async (req, res) => {
10
+ try {
11
+ const botId = parseInt(req.params.botId, 10);
12
+
13
+ const groups = await prisma.group.findMany({
14
+ where: { botId },
15
+ include: { permissions: { include: { permission: true } } },
16
+ orderBy: { name: 'asc' }
17
+ });
18
+
19
+ res.json({
20
+ items: groups,
21
+ total: groups.length
22
+ });
23
+ } catch (error) {
24
+ console.error('[API Error] GET /bots/:botId/groups:', error);
25
+ res.status(500).json({ error: 'Не удалось получить список групп' });
26
+ }
27
+ });
28
+
29
+ router.get('/:botId/groups/:groupId', authenticateUniversal, authorize('management:view'), async (req, res) => {
30
+ try {
31
+ const botId = parseInt(req.params.botId, 10);
32
+ const groupId = parseInt(req.params.groupId, 10);
33
+
34
+ const group = await prisma.group.findFirst({
35
+ where: { id: groupId, botId },
36
+ include: { permissions: { include: { permission: true } } }
37
+ });
38
+
39
+ if (!group) {
40
+ return res.status(404).json({ error: 'Группа не найдена' });
41
+ }
42
+
43
+ res.json(group);
44
+ } catch (error) {
45
+ console.error('[API Error] GET /bots/:botId/groups/:groupId:', error);
46
+ res.status(500).json({ error: 'Не удалось получить группу' });
47
+ }
48
+ });
49
+
50
+ router.post('/:botId/groups/:groupId/permissions', authenticateUniversal, authorize('management:edit'), async (req, res) => {
51
+ try {
52
+ const botId = parseInt(req.params.botId, 10);
53
+ const groupId = parseInt(req.params.groupId, 10);
54
+ const { permissionId } = req.body;
55
+
56
+ const group = await prisma.group.findFirst({ where: { id: groupId, botId } });
57
+ if (!group) {
58
+ return res.status(404).json({ error: 'Группа не найдена' });
59
+ }
60
+
61
+ const permission = await prisma.permission.findFirst({ where: { id: permissionId, botId } });
62
+ if (!permission) {
63
+ return res.status(404).json({ error: 'Право не найдено' });
64
+ }
65
+
66
+ await prisma.groupPermission.upsert({
67
+ where: { groupId_permissionId: { groupId, permissionId } },
68
+ create: { groupId, permissionId },
69
+ update: {}
70
+ });
71
+
72
+ res.json({ success: true });
73
+ } catch (error) {
74
+ console.error('[API Error] POST /bots/:botId/groups/:groupId/permissions:', error);
75
+ res.status(500).json({ error: 'Не удалось добавить право в группу' });
76
+ }
77
+ });
78
+
79
+ router.delete('/:botId/groups/:groupId/permissions/:permissionId', authenticateUniversal, authorize('management:edit'), async (req, res) => {
80
+ try {
81
+ const botId = parseInt(req.params.botId, 10);
82
+ const groupId = parseInt(req.params.groupId, 10);
83
+ const permissionId = parseInt(req.params.permissionId, 10);
84
+
85
+ const group = await prisma.group.findFirst({ where: { id: groupId, botId } });
86
+ if (!group) {
87
+ return res.status(404).json({ error: 'Группа не найдена' });
88
+ }
89
+
90
+ await prisma.groupPermission.deleteMany({
91
+ where: { groupId, permissionId }
92
+ });
93
+
94
+ res.json({ success: true });
95
+ } catch (error) {
96
+ console.error('[API Error] DELETE /bots/:botId/groups/:groupId/permissions/:permissionId:', error);
97
+ res.status(500).json({ error: 'Не удалось удалить право из группы' });
98
+ }
99
+ });
100
+
101
+ router.post('/:botId/groups/:groupId/users', authenticateUniversal, authorize('management:edit'), async (req, res) => {
102
+ try {
103
+ const botId = parseInt(req.params.botId, 10);
104
+ const groupId = parseInt(req.params.groupId, 10);
105
+ const { username } = req.body;
106
+
107
+ const group = await prisma.group.findFirst({ where: { id: groupId, botId } });
108
+ if (!group) {
109
+ return res.status(404).json({ error: 'Группа не найдена' });
110
+ }
111
+
112
+ let user = await prisma.user.findFirst({ where: { botId, username } });
113
+ if (!user) {
114
+ user = await prisma.user.create({
115
+ data: { botId, username, isBlacklisted: false }
116
+ });
117
+ }
118
+
119
+ await prisma.userGroup.upsert({
120
+ where: { userId_groupId: { userId: user.id, groupId } },
121
+ create: { userId: user.id, groupId },
122
+ update: {}
123
+ });
124
+
125
+ botManager.invalidateUserCache(botId, username);
126
+ UserService.clearCache(username, botId);
127
+
128
+ res.json({ success: true });
129
+ } catch (error) {
130
+ console.error('[API Error] POST /bots/:botId/groups/:groupId/users:', error);
131
+ res.status(500).json({ error: 'Не удалось добавить пользователя в группу' });
132
+ }
133
+ });
134
+
135
+ router.delete('/:botId/groups/:groupId/users/:username', authenticateUniversal, authorize('management:edit'), async (req, res) => {
136
+ try {
137
+ const botId = parseInt(req.params.botId, 10);
138
+ const groupId = parseInt(req.params.groupId, 10);
139
+ const { username } = req.params;
140
+
141
+ const group = await prisma.group.findFirst({ where: { id: groupId, botId } });
142
+ if (!group) {
143
+ return res.status(404).json({ error: 'Группа не найдена' });
144
+ }
145
+
146
+ const user = await prisma.user.findFirst({ where: { botId, username } });
147
+ if (!user) {
148
+ return res.status(404).json({ error: 'Пользователь не найден' });
149
+ }
150
+
151
+ await prisma.userGroup.deleteMany({
152
+ where: { userId: user.id, groupId }
153
+ });
154
+
155
+ botManager.invalidateUserCache(botId, username);
156
+ UserService.clearCache(username, botId);
157
+
158
+ res.json({ success: true });
159
+ } catch (error) {
160
+ console.error('[API Error] DELETE /bots/:botId/groups/:groupId/users/:username:', error);
161
+ res.status(500).json({ error: 'Не удалось удалить пользователя из группы' });
162
+ }
163
+ });
164
+
165
+ module.exports = router;
@@ -0,0 +1,108 @@
1
+ const express = require('express');
2
+ const { authenticateUniversal, authorize } = require('../middleware/auth');
3
+ const botHistoryStore = require('../../core/BotHistoryStore');
4
+
5
+ const router = express.Router();
6
+
7
+ router.get('/:botId/chat', authenticateUniversal, authorize('bot:list'), async (req, res) => {
8
+ try {
9
+ const botId = parseInt(req.params.botId);
10
+ const { type, username, search, from, to, limit, offset } = req.query;
11
+
12
+ const result = botHistoryStore.getChatHistory(botId, {
13
+ type,
14
+ username,
15
+ search,
16
+ from,
17
+ to,
18
+ limit: limit || 100,
19
+ offset: offset || 0
20
+ });
21
+
22
+ res.json({
23
+ messages: result.messages,
24
+ total: result.total,
25
+ pagination: {
26
+ total: result.total,
27
+ limit: parseInt(limit) || 100,
28
+ offset: parseInt(offset) || 0,
29
+ hasMore: (parseInt(offset) || 0) + (parseInt(limit) || 100) < result.total
30
+ }
31
+ });
32
+ } catch (error) {
33
+ console.error('[API Error] GET /bots/:botId/chat:', error);
34
+ res.status(500).json({
35
+ success: false,
36
+ error: 'Не удалось получить историю чата.'
37
+ });
38
+ }
39
+ });
40
+
41
+ router.get('/:botId/commands', authenticateUniversal, authorize('bot:list'), async (req, res) => {
42
+ try {
43
+ const botId = parseInt(req.params.botId);
44
+ const { username, command, success, from, to, limit, offset } = req.query;
45
+
46
+ const result = botHistoryStore.getCommandLogs(botId, {
47
+ username,
48
+ command,
49
+ success,
50
+ from,
51
+ to,
52
+ limit: limit || 100,
53
+ offset: offset || 0
54
+ });
55
+
56
+ res.json({
57
+ logs: result.logs,
58
+ total: result.total,
59
+ pagination: {
60
+ total: result.total,
61
+ limit: parseInt(limit) || 100,
62
+ offset: parseInt(offset) || 0,
63
+ hasMore: (parseInt(offset) || 0) + (parseInt(limit) || 100) < result.total
64
+ }
65
+ });
66
+ } catch (error) {
67
+ console.error('[API Error] GET /bots/:botId/commands:', error);
68
+ res.status(500).json({
69
+ success: false,
70
+ error: 'Не удалось получить логи команд.'
71
+ });
72
+ }
73
+ });
74
+
75
+ router.get('/:botId/stats', authenticateUniversal, authorize('bot:list'), async (req, res) => {
76
+ try {
77
+ const botId = parseInt(req.params.botId);
78
+ const stats = botHistoryStore.getStats(botId);
79
+
80
+ res.json(stats);
81
+ } catch (error) {
82
+ console.error('[API Error] GET /bots/:botId/stats:', error);
83
+ res.status(500).json({
84
+ success: false,
85
+ error: 'Не удалось получить статистику.'
86
+ });
87
+ }
88
+ });
89
+
90
+ router.delete('/:botId/clear', authenticateUniversal, authorize('bot:list'), async (req, res) => {
91
+ try {
92
+ const botId = parseInt(req.params.botId);
93
+ botHistoryStore.clearBot(botId);
94
+
95
+ res.json({
96
+ success: true,
97
+ message: 'История бота очищена.'
98
+ });
99
+ } catch (error) {
100
+ console.error('[API Error] DELETE /bots/:botId/clear:', error);
101
+ res.status(500).json({
102
+ success: false,
103
+ error: 'Не удалось очистить историю.'
104
+ });
105
+ }
106
+ });
107
+
108
+ module.exports = router;
@@ -0,0 +1,99 @@
1
+ const express = require('express');
2
+ const prisma = require('../../lib/prisma');
3
+ const { authenticateUniversal, authorize } = require('../middleware/auth');
4
+
5
+ const router = express.Router();
6
+
7
+ router.get('/:botId/permissions', authenticateUniversal, authorize('management:view'), async (req, res) => {
8
+ try {
9
+ const botId = parseInt(req.params.botId, 10);
10
+
11
+ const permissions = await prisma.permission.findMany({
12
+ where: { botId },
13
+ orderBy: { name: 'asc' }
14
+ });
15
+
16
+ res.json({
17
+ items: permissions,
18
+ total: permissions.length
19
+ });
20
+ } catch (error) {
21
+ console.error('[API Error] GET /bots/:botId/permissions:', error);
22
+ res.status(500).json({ error: 'Не удалось получить список прав' });
23
+ }
24
+ });
25
+
26
+ router.get('/:botId/permissions/:permissionId', authenticateUniversal, authorize('management:view'), async (req, res) => {
27
+ try {
28
+ const botId = parseInt(req.params.botId, 10);
29
+ const permissionId = parseInt(req.params.permissionId, 10);
30
+
31
+ const permission = await prisma.permission.findFirst({
32
+ where: { id: permissionId, botId }
33
+ });
34
+
35
+ if (!permission) {
36
+ return res.status(404).json({ error: 'Право не найдено' });
37
+ }
38
+
39
+ res.json(permission);
40
+ } catch (error) {
41
+ console.error('[API Error] GET /bots/:botId/permissions/:permissionId:', error);
42
+ res.status(500).json({ error: 'Не удалось получить право' });
43
+ }
44
+ });
45
+
46
+ router.put('/:botId/permissions/:permissionId', authenticateUniversal, authorize('management:edit'), async (req, res) => {
47
+ try {
48
+ const botId = parseInt(req.params.botId, 10);
49
+ const permissionId = parseInt(req.params.permissionId, 10);
50
+ const { name, description } = req.body;
51
+
52
+ const permission = await prisma.permission.findFirst({
53
+ where: { id: permissionId, botId }
54
+ });
55
+
56
+ if (!permission) {
57
+ return res.status(404).json({ error: 'Право не найдено' });
58
+ }
59
+
60
+ const updateData = {};
61
+ if (name !== undefined) updateData.name = name;
62
+ if (description !== undefined) updateData.description = description;
63
+
64
+ const updated = await prisma.permission.update({
65
+ where: { id: permissionId },
66
+ data: updateData
67
+ });
68
+
69
+ res.json(updated);
70
+ } catch (error) {
71
+ console.error('[API Error] PUT /bots/:botId/permissions/:permissionId:', error);
72
+ res.status(500).json({ error: 'Не удалось обновить право' });
73
+ }
74
+ });
75
+
76
+ router.delete('/:botId/permissions/:permissionId', authenticateUniversal, authorize('management:edit'), async (req, res) => {
77
+ try {
78
+ const botId = parseInt(req.params.botId, 10);
79
+ const permissionId = parseInt(req.params.permissionId, 10);
80
+
81
+ const permission = await prisma.permission.findFirst({
82
+ where: { id: permissionId, botId }
83
+ });
84
+
85
+ if (!permission) {
86
+ return res.status(404).json({ error: 'Право не найдено' });
87
+ }
88
+
89
+ await prisma.groupPermission.deleteMany({ where: { permissionId } });
90
+ await prisma.permission.delete({ where: { id: permissionId } });
91
+
92
+ res.json({ success: true });
93
+ } catch (error) {
94
+ console.error('[API Error] DELETE /bots/:botId/permissions/:permissionId:', error);
95
+ res.status(500).json({ error: 'Не удалось удалить право' });
96
+ }
97
+ });
98
+
99
+ module.exports = router;