blockmine 1.18.4 → 1.19.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 (35) hide show
  1. package/.kiro/steering/product.md +27 -0
  2. package/.kiro/steering/structure.md +89 -0
  3. package/.kiro/steering/tech.md +94 -0
  4. package/CHANGELOG.md +147 -112
  5. package/backend/cli.js +59 -57
  6. package/backend/prisma/migrations/20250701190321_add/migration.sql +2 -2
  7. package/backend/prisma/migrations/20250709150611_add_run_on_startup_to_tasks/migration.sql +21 -21
  8. package/backend/prisma/migrations/20250709151124_make_cron_pattern_optional/migration.sql +21 -21
  9. package/backend/prisma/migrations/20250718181335_add_plugin_data_store/migration.sql +14 -14
  10. package/backend/prisma/migrations/20250719115906_add_plugin_owner_to_graphs/migration.sql +45 -45
  11. package/backend/prisma/migrations/20250723160648_add_bot_sort_order/migration.sql +2 -2
  12. package/backend/prisma/migrations/20250816083216_add_panel_user_bot_access/migration.sql +30 -0
  13. package/backend/prisma/migrations/migration_lock.toml +2 -2
  14. package/backend/prisma/schema.prisma +244 -229
  15. package/backend/src/api/middleware/botAccess.js +35 -0
  16. package/backend/src/api/routes/auth.js +633 -595
  17. package/backend/src/api/routes/bots.js +294 -179
  18. package/backend/src/api/routes/eventGraphs.js +459 -459
  19. package/backend/src/api/routes/pluginIde.js +6 -2
  20. package/backend/src/api/routes/servers.js +27 -0
  21. package/backend/src/core/BotManager.js +0 -1
  22. package/backend/src/core/BotProcess.js +1 -2
  23. package/backend/src/core/GraphExecutionEngine.js +917 -917
  24. package/backend/src/core/PluginLoader.js +208 -86
  25. package/backend/src/core/PluginManager.js +465 -427
  26. package/backend/src/core/commands/dev.js +6 -1
  27. package/backend/src/real-time/presence.js +74 -0
  28. package/backend/src/real-time/socketHandler.js +2 -0
  29. package/backend/src/server.js +193 -186
  30. package/frontend/dist/assets/index-BFd7YoAj.css +1 -0
  31. package/frontend/dist/assets/index-DxdxTe6I.js +8352 -0
  32. package/frontend/dist/index.html +2 -2
  33. package/package.json +1 -1
  34. package/frontend/dist/assets/index-CA3XrPPP.css +0 -1
  35. package/frontend/dist/assets/index-CM9ljR30.js +0 -8352
@@ -13,6 +13,7 @@ const { randomUUID } = require('crypto');
13
13
  const eventGraphsRouter = require('./eventGraphs');
14
14
  const pluginIdeRouter = require('./pluginIde');
15
15
  const { deepMergeSettings } = require('../../core/utils/settingsMerger');
16
+ const { checkBotAccess } = require('../middleware/botAccess');
16
17
 
17
18
  const multer = require('multer');
18
19
  const archiver = require('archiver');
@@ -23,6 +24,8 @@ const upload = multer({ storage: multer.memoryStorage() });
23
24
 
24
25
  const router = express.Router();
25
26
 
27
+ router.use('/:botId(\\d+)/*', authenticate, (req, res, next) => checkBotAccess(req, res, next));
28
+
26
29
  const conditionalRestartAuth = (req, res, next) => {
27
30
  if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
28
31
  console.log('[Debug] Роут перезапуска бота доступен без проверки прав');
@@ -60,18 +63,16 @@ const conditionalStartStopAuth = (req, res, next) => {
60
63
  };
61
64
 
62
65
  const conditionalListAuth = (req, res, next) => {
63
- if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
64
- console.log('[Debug] Роут списка ботов/состояния доступен без проверки прав');
65
- return next();
66
- }
67
-
68
66
  return authenticate(req, res, (err) => {
69
67
  if (err) return next(err);
68
+ if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
69
+ return next();
70
+ }
70
71
  return authorize('bot:list')(req, res, next);
71
72
  });
72
73
  };
73
74
 
74
- router.post('/:id/restart', conditionalRestartAuth, async (req, res) => {
75
+ router.post('/:id/restart', conditionalRestartAuth, authenticate, checkBotAccess, async (req, res) => {
75
76
  try {
76
77
  const botId = parseInt(req.params.id, 10);
77
78
  botManager.stopBot(botId);
@@ -89,7 +90,7 @@ router.post('/:id/restart', conditionalRestartAuth, async (req, res) => {
89
90
  }
90
91
  });
91
92
 
92
- router.post('/:id/chat', conditionalChatAuth, (req, res) => {
93
+ router.post('/:id/chat', conditionalChatAuth, authenticate, checkBotAccess, (req, res) => {
93
94
  try {
94
95
  const botId = parseInt(req.params.id, 10);
95
96
  const { message } = req.body;
@@ -100,7 +101,7 @@ router.post('/:id/chat', conditionalChatAuth, (req, res) => {
100
101
  } catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
101
102
  });
102
103
 
103
- router.post('/:id/start', conditionalStartStopAuth, async (req, res) => {
104
+ router.post('/:id/start', conditionalStartStopAuth, authenticate, checkBotAccess, async (req, res) => {
104
105
  try {
105
106
  const botId = parseInt(req.params.id, 10);
106
107
  const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
@@ -115,7 +116,7 @@ router.post('/:id/start', conditionalStartStopAuth, async (req, res) => {
115
116
  }
116
117
  });
117
118
 
118
- router.post('/:id/stop', conditionalStartStopAuth, (req, res) => {
119
+ router.post('/:id/stop', conditionalStartStopAuth, authenticate, checkBotAccess, (req, res) => {
119
120
  try {
120
121
  const botId = parseInt(req.params.id, 10);
121
122
  botManager.stopBot(botId);
@@ -135,15 +136,37 @@ router.get('/', conditionalListAuth, async (req, res) => {
135
136
 
136
137
  if (botsWithoutSortOrder.length > 0) {
137
138
  console.log(`[API] Обновляем sortOrder для ${botsWithoutSortOrder.length} ботов`);
139
+
140
+ const maxSortOrder = await prisma.bot.aggregate({
141
+ _max: { sortOrder: true }
142
+ });
143
+
144
+ let nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
145
+
138
146
  for (const bot of botsWithoutSortOrder) {
139
147
  await prisma.bot.update({
140
148
  where: { id: bot.id },
141
- data: { sortOrder: bot.id }
149
+ data: { sortOrder: nextSortOrder }
142
150
  });
151
+ console.log(`[API] Установлен sortOrder ${nextSortOrder} для бота ${bot.id}`);
152
+ nextSortOrder++;
153
+ }
154
+ }
155
+
156
+ let whereFilter = {};
157
+ if (req.user && typeof req.user.userId === 'number') {
158
+ const panelUser = await prisma.panelUser.findUnique({
159
+ where: { id: req.user.userId },
160
+ include: { botAccess: { select: { botId: true } } }
161
+ });
162
+ if (panelUser && panelUser.allBots === false) {
163
+ const allowedIds = panelUser.botAccess.map(a => a.botId);
164
+ whereFilter = { id: { in: allowedIds.length ? allowedIds : [-1] } };
143
165
  }
144
166
  }
145
167
 
146
168
  const bots = await prisma.bot.findMany({
169
+ where: whereFilter,
147
170
  include: { server: true },
148
171
  orderBy: { sortOrder: 'asc' }
149
172
  });
@@ -161,7 +184,119 @@ router.get('/state', conditionalListAuth, (req, res) => {
161
184
  } catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
162
185
  });
163
186
 
164
- router.get('/:id/logs', conditionalListAuth, (req, res) => {
187
+ router.put('/bulk-proxy-update', authenticate, authorize('bot:update'), async (req, res) => {
188
+ try {
189
+ const { botIds, proxySettings } = req.body;
190
+
191
+ if (!Array.isArray(botIds) || botIds.length === 0) {
192
+ return res.status(400).json({ error: 'Bot IDs array is required and cannot be empty' });
193
+ }
194
+
195
+ if (!proxySettings || !proxySettings.proxyHost || !proxySettings.proxyPort) {
196
+ return res.status(400).json({ error: 'Proxy host and port are required' });
197
+ }
198
+
199
+ if (proxySettings.proxyPort < 1 || proxySettings.proxyPort > 65535) {
200
+ return res.status(400).json({ error: 'Proxy port must be between 1 and 65535' });
201
+ }
202
+
203
+ const accessibleBots = [];
204
+ const inaccessibleBots = [];
205
+
206
+ for (const botId of botIds) {
207
+ try {
208
+ const userId = req.user?.userId;
209
+ if (!userId) {
210
+ inaccessibleBots.push(botId);
211
+ continue;
212
+ }
213
+
214
+ const botIdInt = parseInt(botId, 10);
215
+ if (isNaN(botIdInt)) {
216
+ inaccessibleBots.push(botId);
217
+ continue;
218
+ }
219
+
220
+ const user = await prisma.panelUser.findUnique({
221
+ where: { id: userId },
222
+ include: { botAccess: { select: { botId: true } } }
223
+ });
224
+
225
+ if (!user) {
226
+ inaccessibleBots.push(botId);
227
+ continue;
228
+ }
229
+
230
+ if (user.allBots !== false || user.botAccess.some((a) => a.botId === botIdInt)) {
231
+ accessibleBots.push(botIdInt);
232
+ } else {
233
+ inaccessibleBots.push(botId);
234
+ }
235
+ } catch (error) {
236
+ console.error(`Error checking access for bot ${botId}:`, error);
237
+ inaccessibleBots.push(botId);
238
+ }
239
+ }
240
+
241
+ if (accessibleBots.length === 0) {
242
+ return res.status(403).json({ error: 'No accessible bots in the provided list' });
243
+ }
244
+
245
+ const encryptedSettings = {
246
+ proxyHost: proxySettings.proxyHost.trim(),
247
+ proxyPort: parseInt(proxySettings.proxyPort),
248
+ proxyUsername: proxySettings.proxyUsername ? proxySettings.proxyUsername.trim() : null,
249
+ proxyPassword: proxySettings.proxyPassword ? encrypt(proxySettings.proxyPassword) : null
250
+ };
251
+
252
+ const updatedBots = await prisma.$transaction(
253
+ accessibleBots.map(botId =>
254
+ prisma.bot.update({
255
+ where: { id: parseInt(botId) },
256
+ data: encryptedSettings,
257
+ include: {
258
+ server: {
259
+ select: {
260
+ id: true,
261
+ name: true,
262
+ host: true,
263
+ port: true,
264
+ version: true
265
+ }
266
+ }
267
+ }
268
+ })
269
+ )
270
+ );
271
+
272
+ if (req.io) {
273
+ req.io.emit('bots-updated', updatedBots);
274
+ }
275
+
276
+ res.json({
277
+ success: true,
278
+ message: `Proxy settings updated for ${updatedBots.length} bot(s)`,
279
+ updatedBots: updatedBots.map(bot => ({
280
+ id: bot.id,
281
+ username: bot.username,
282
+ proxyHost: bot.proxyHost,
283
+ proxyPort: bot.proxyPort,
284
+ server: bot.server
285
+ })),
286
+ inaccessibleBots: inaccessibleBots,
287
+ errors: inaccessibleBots.length > 0 ? [`Access denied to ${inaccessibleBots.length} bot(s)`] : []
288
+ });
289
+
290
+ } catch (error) {
291
+ console.error('Bulk proxy update error:', error);
292
+ res.status(500).json({
293
+ error: 'Failed to update proxy settings',
294
+ details: error.message
295
+ });
296
+ }
297
+ });
298
+
299
+ router.get('/:id/logs', conditionalListAuth, authenticate, checkBotAccess, (req, res) => {
165
300
  try {
166
301
  const botId = parseInt(req.params.id, 10);
167
302
  const { limit = 50, offset = 0 } = req.query;
@@ -264,7 +399,7 @@ router.post('/', authorize('bot:create'), async (req, res) => {
264
399
  }
265
400
  });
266
401
 
267
- router.put('/:id', authorize('bot:update'), async (req, res) => {
402
+ router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
268
403
  try {
269
404
  const {
270
405
  username, password, prefix, serverId, note, owners,
@@ -337,76 +472,58 @@ router.put('/:id', authorize('bot:update'), async (req, res) => {
337
472
  }
338
473
  });
339
474
 
340
- router.put('/:id/sort-order', authorize('bot:update'), async (req, res) => {
475
+ router.put('/:id/sort-order', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
341
476
  try {
342
- const { newPosition } = req.body;
477
+ const { newPosition, oldIndex, newIndex } = req.body;
343
478
  const botId = parseInt(req.params.id, 10);
344
479
 
345
- console.log(`[API] Запрос на изменение порядка бота ${botId} на позицию ${newPosition}`);
480
+ console.log(`[API] Запрос на изменение порядка бота ${botId}: oldIndex=${oldIndex}, newIndex=${newIndex}, newPosition=${newPosition}`);
346
481
 
347
- if (isNaN(botId) || typeof newPosition !== 'number') {
348
- console.log(`[API] Неверные параметры: botId=${botId}, newPosition=${newPosition}`);
349
- return res.status(400).json({ error: 'Неверные параметры' });
482
+ if (isNaN(botId)) {
483
+ console.log(`[API] Неверный botId: ${botId}`);
484
+ return res.status(400).json({ error: 'Неверный ID бота' });
350
485
  }
351
486
 
352
- const currentBot = await prisma.bot.findUnique({
353
- where: { id: botId },
354
- select: { sortOrder: true }
487
+ const allBots = await prisma.bot.findMany({
488
+ orderBy: { sortOrder: 'asc' },
489
+ select: { id: true, sortOrder: true }
355
490
  });
356
491
 
357
- if (!currentBot) {
492
+ console.log(`[API] Всего ботов: ${allBots.length}`);
493
+ const currentBotIndex = allBots.findIndex(bot => bot.id === botId);
494
+ if (currentBotIndex === -1) {
358
495
  console.log(`[API] Бот ${botId} не найден`);
359
496
  return res.status(404).json({ error: 'Бот не найден' });
360
497
  }
361
498
 
362
- const currentPosition = currentBot.sortOrder;
363
- console.log(`[API] Текущая позиция бота ${botId}: ${currentPosition}, новая позиция: ${newPosition}`);
364
-
365
- if (newPosition === currentPosition) {
499
+ if (newIndex < 0 || newIndex >= allBots.length) {
500
+ console.log(`[API] Неверная новая позиция: ${newIndex}`);
501
+ return res.status(400).json({ error: 'Неверная позиция' });
502
+ }
503
+
504
+ if (currentBotIndex === newIndex) {
366
505
  console.log(`[API] Позиция не изменилась для бота ${botId}`);
367
506
  return res.json({ success: true, message: 'Позиция не изменилась' });
368
507
  }
508
+ const reorderedBots = [...allBots];
509
+ const [movedBot] = reorderedBots.splice(currentBotIndex, 1);
510
+ reorderedBots.splice(newIndex, 0, movedBot);
369
511
 
370
- if (newPosition > currentPosition) {
371
- console.log(`[API] Перемещаем бота ${botId} вниз с позиции ${currentPosition} на ${newPosition}`);
372
- const updateResult = await prisma.bot.updateMany({
373
- where: {
374
- sortOrder: {
375
- gt: currentPosition,
376
- lte: newPosition
377
- }
378
- },
379
- data: {
380
- sortOrder: {
381
- decrement: 1
382
- }
383
- }
384
- });
385
- console.log(`[API] Обновлено ${updateResult.count} ботов при перемещении вниз`);
386
- } else {
387
- console.log(`[API] Перемещаем бота ${botId} вверх с позиции ${currentPosition} на ${newPosition}`);
388
- const updateResult = await prisma.bot.updateMany({
389
- where: {
390
- sortOrder: {
391
- gte: newPosition,
392
- lt: currentPosition
393
- }
394
- },
395
- data: {
396
- sortOrder: {
397
- increment: 1
398
- }
399
- }
400
- });
401
- console.log(`[API] Обновлено ${updateResult.count} ботов при перемещении вверх`);
512
+ console.log(`[API] Обновляем порядок для всех ботов`);
513
+ for (let i = 0; i < reorderedBots.length; i++) {
514
+ const bot = reorderedBots[i];
515
+ const newSortOrder = i + 1; // 1-based позиции
516
+
517
+ if (bot.sortOrder !== newSortOrder) {
518
+ await prisma.bot.update({
519
+ where: { id: bot.id },
520
+ data: { sortOrder: newSortOrder }
521
+ });
522
+ console.log(`[API] Обновлен бот ${bot.id}: sortOrder ${bot.sortOrder} -> ${newSortOrder}`);
523
+ }
402
524
  }
403
525
 
404
- await prisma.bot.update({
405
- where: { id: botId },
406
- data: { sortOrder: newPosition }
407
- });
408
-
409
- console.log(`[API] Успешно обновлен порядок бота ${botId} на позицию ${newPosition}`);
526
+ console.log(`[API] Успешно обновлен порядок бота ${botId}`);
410
527
  res.json({ success: true, message: 'Порядок ботов обновлен' });
411
528
  } catch (error) {
412
529
  console.error("[API Error] /bots sort-order PUT:", error);
@@ -414,7 +531,7 @@ router.put('/:id/sort-order', authorize('bot:update'), async (req, res) => {
414
531
  }
415
532
  });
416
533
 
417
- router.delete('/:id', authorize('bot:delete'), async (req, res) => {
534
+ router.delete('/:id', authenticate, checkBotAccess, authorize('bot:delete'), async (req, res) => {
418
535
  try {
419
536
  const botId = parseInt(req.params.id, 10);
420
537
  if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
@@ -433,7 +550,7 @@ router.get('/servers', authorize('bot:list'), async (req, res) => {
433
550
  }
434
551
  });
435
552
 
436
- router.get('/:botId/plugins', authorize('plugin:list'), async (req, res) => {
553
+ router.get('/:botId/plugins', authenticate, checkBotAccess, authorize('plugin:list'), async (req, res) => {
437
554
  try {
438
555
  const botId = parseInt(req.params.botId);
439
556
  const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
@@ -441,7 +558,7 @@ router.get('/:botId/plugins', authorize('plugin:list'), async (req, res) => {
441
558
  } catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
442
559
  });
443
560
 
444
- router.post('/:botId/plugins/install/github', authorize('plugin:install'), async (req, res) => {
561
+ router.post('/:botId/plugins/install/github', authenticate, checkBotAccess, authorize('plugin:install'), async (req, res) => {
445
562
  const { botId } = req.params;
446
563
  const { repoUrl } = req.body;
447
564
  try {
@@ -452,7 +569,7 @@ router.post('/:botId/plugins/install/github', authorize('plugin:install'), async
452
569
  }
453
570
  });
454
571
 
455
- router.post('/:botId/plugins/install/local', authorize('plugin:install'), async (req, res) => {
572
+ router.post('/:botId/plugins/install/local', authenticate, checkBotAccess, authorize('plugin:install'), async (req, res) => {
456
573
  const { botId } = req.params;
457
574
  const { path } = req.body;
458
575
  try {
@@ -463,7 +580,7 @@ router.post('/:botId/plugins/install/local', authorize('plugin:install'), async
463
580
  }
464
581
  });
465
582
 
466
- router.delete('/:botId/plugins/:pluginId', authorize('plugin:delete'), async (req, res) => {
583
+ router.delete('/:botId/plugins/:pluginId', authenticate, checkBotAccess, authorize('plugin:delete'), async (req, res) => {
467
584
  const { pluginId } = req.params;
468
585
  try {
469
586
  await pluginManager.deletePlugin(parseInt(pluginId));
@@ -473,7 +590,7 @@ router.delete('/:botId/plugins/:pluginId', authorize('plugin:delete'), async (re
473
590
  }
474
591
  });
475
592
 
476
- router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view'), async (req, res) => {
593
+ router.get('/:botId/plugins/:pluginId/settings', authenticate, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
477
594
  try {
478
595
  const pluginId = parseInt(req.params.pluginId);
479
596
  const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
@@ -531,120 +648,107 @@ router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view
531
648
  }
532
649
  });
533
650
 
534
- // Вкладка Данные плагина (PluginDataStore)
535
- router.get('/:botId/plugins/:pluginId/data', authorize('plugin:settings:view'), async (req, res) => {
536
- try {
537
- const pluginId = parseInt(req.params.pluginId);
538
- const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
539
- if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
540
-
541
- const rows = await prisma.pluginDataStore.findMany({
542
- where: { botId: plugin.botId, pluginName: plugin.name },
543
- orderBy: { updatedAt: 'desc' }
544
- });
545
-
546
- const result = rows.map(r => {
547
- let value;
548
- try { value = JSON.parse(r.value); } catch { value = r.value; }
549
- return { key: r.key, value, createdAt: r.createdAt, updatedAt: r.updatedAt };
550
- });
551
- res.json(result);
552
- } catch (error) {
553
- console.error('[API Error] GET plugin data:', error);
554
- res.status(500).json({ error: 'Не удалось получить данные плагина' });
555
- }
556
- });
651
+ router.get('/:botId/plugins/:pluginId/data', authenticate, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
652
+ try {
653
+ const pluginId = parseInt(req.params.pluginId);
654
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
655
+ if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
557
656
 
558
- router.get('/:botId/plugins/:pluginId/data/:key', authorize('plugin:settings:view'), async (req, res) => {
559
- try {
560
- const pluginId = parseInt(req.params.pluginId);
561
- const { key } = req.params;
562
- const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
563
- if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
657
+ const rows = await prisma.pluginDataStore.findMany({
658
+ where: { botId: plugin.botId, pluginName: plugin.name },
659
+ orderBy: { updatedAt: 'desc' }
660
+ });
564
661
 
565
- const row = await prisma.pluginDataStore.findUnique({
566
- where: {
567
- pluginName_botId_key: {
568
- pluginName: plugin.name,
569
- botId: plugin.botId,
570
- key
571
- }
572
- }
573
- });
574
- if (!row) return res.status(404).json({ error: 'Ключ не найден' });
575
- let value; try { value = JSON.parse(row.value); } catch { value = row.value; }
576
- res.json({ key: row.key, value, createdAt: row.createdAt, updatedAt: row.updatedAt });
577
- } catch (error) {
578
- console.error('[API Error] GET plugin data by key:', error);
579
- res.status(500).json({ error: 'Не удалось получить значение по ключу' });
580
- }
662
+ const result = rows.map(r => {
663
+ let value;
664
+ try { value = JSON.parse(r.value); } catch { value = r.value; }
665
+ return { key: r.key, value, createdAt: r.createdAt, updatedAt: r.updatedAt };
666
+ });
667
+ res.json(result);
668
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить данные плагина' }); }
581
669
  });
582
670
 
583
- router.put('/:botId/plugins/:pluginId/data/:key', authorize('plugin:settings:edit'), async (req, res) => {
584
- try {
585
- const pluginId = parseInt(req.params.pluginId);
586
- const { key } = req.params;
587
- const { value } = req.body;
588
- const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
589
- if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
590
-
591
- const jsonValue = JSON.stringify(value ?? null);
592
- const upserted = await prisma.pluginDataStore.upsert({
593
- where: {
594
- pluginName_botId_key: {
595
- pluginName: plugin.name,
596
- botId: plugin.botId,
597
- key
598
- }
599
- },
600
- update: { value: jsonValue },
601
- create: { pluginName: plugin.name, botId: plugin.botId, key, value: jsonValue }
602
- });
603
- let parsed; try { parsed = JSON.parse(upserted.value); } catch { parsed = upserted.value; }
604
- res.json({ key: upserted.key, value: parsed, createdAt: upserted.createdAt, updatedAt: upserted.updatedAt });
605
- } catch (error) {
606
- console.error('[API Error] PUT plugin data by key:', error);
607
- res.status(500).json({ error: 'Не удалось сохранить значение' });
608
- }
671
+ router.get('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
672
+ try {
673
+ const pluginId = parseInt(req.params.pluginId);
674
+ const { key } = req.params;
675
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
676
+ if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
677
+
678
+ const row = await prisma.pluginDataStore.findUnique({
679
+ where: {
680
+ pluginName_botId_key: {
681
+ pluginName: plugin.name,
682
+ botId: plugin.botId,
683
+ key
684
+ }
685
+ }
686
+ });
687
+ if (!row) return res.status(404).json({ error: 'Ключ не найден' });
688
+ let value; try { value = JSON.parse(row.value); } catch { value = row.value; }
689
+ res.json({ key: row.key, value, createdAt: row.createdAt, updatedAt: row.updatedAt });
690
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить значение по ключу' }); }
609
691
  });
610
692
 
611
- router.delete('/:botId/plugins/:pluginId/data/:key', authorize('plugin:settings:edit'), async (req, res) => {
612
- try {
613
- const pluginId = parseInt(req.params.pluginId);
614
- const { key } = req.params;
615
- const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
616
- if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
693
+ router.put('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
694
+ try {
695
+ const pluginId = parseInt(req.params.pluginId);
696
+ const { key } = req.params;
697
+ const { value } = req.body;
698
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
699
+ if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
700
+
701
+ const jsonValue = JSON.stringify(value ?? null);
702
+ const upserted = await prisma.pluginDataStore.upsert({
703
+ where: {
704
+ pluginName_botId_key: {
705
+ pluginName: plugin.name,
706
+ botId: plugin.botId,
707
+ key
708
+ }
709
+ },
710
+ update: { value: jsonValue },
711
+ create: { pluginName: plugin.name, botId: plugin.botId, key, value: jsonValue }
712
+ });
713
+ let parsed; try { parsed = JSON.parse(upserted.value); } catch { parsed = upserted.value; }
714
+ res.json({ key: upserted.key, value: parsed, createdAt: upserted.createdAt, updatedAt: upserted.updatedAt });
715
+ } catch (error) { res.status(500).json({ error: 'Не удалось сохранить значение' }); }
716
+ });
617
717
 
618
- await prisma.pluginDataStore.delete({
619
- where: {
620
- pluginName_botId_key: {
621
- pluginName: plugin.name,
622
- botId: plugin.botId,
623
- key
624
- }
625
- }
626
- });
627
- res.status(204).send();
628
- } catch (error) {
629
- console.error('[API Error] DELETE plugin data by key:', error);
630
- res.status(500).json({ error: 'Не удалось удалить значение' });
631
- }
718
+ router.delete('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
719
+ try {
720
+ const pluginId = parseInt(req.params.pluginId);
721
+ const { key } = req.params;
722
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
723
+ if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
724
+
725
+ await prisma.pluginDataStore.delete({
726
+ where: {
727
+ pluginName_botId_key: {
728
+ pluginName: plugin.name,
729
+ botId: plugin.botId,
730
+ key
731
+ }
732
+ }
733
+ });
734
+ res.status(204).send();
735
+ } catch (error) { res.status(500).json({ error: 'Не удалось удалить значение' }); }
632
736
  });
633
737
 
634
- router.put('/:botId/plugins/:pluginId', authorize('plugin:settings:edit'), async (req, res) => {
635
- try {
636
- const pluginId = parseInt(req.params.pluginId);
637
- const { isEnabled, settings } = req.body;
638
- const dataToUpdate = {};
639
- if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
640
- if (settings) dataToUpdate.settings = JSON.stringify(settings);
641
- if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
642
- const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
643
- res.json(updated);
644
- } catch (error) { res.status(500).json({ error: 'Не удалось обновить плагин' }); }
738
+ router.put('/:botId/plugins/:pluginId', authenticate, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
739
+ try {
740
+ const pluginId = parseInt(req.params.pluginId);
741
+ const { isEnabled, settings } = req.body;
742
+ const dataToUpdate = {};
743
+ if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
744
+ if (settings) dataToUpdate.settings = JSON.stringify(settings);
745
+ if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
746
+ const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
747
+ res.json(updated);
748
+ } catch (error) { res.status(500).json({ error: 'Не удалось обновить плагин' }); }
645
749
  });
646
750
 
647
- router.get('/:botId/management-data', authorize('management:view'), async (req, res) => {
751
+ router.get('/:botId/management-data', authenticate, checkBotAccess, authorize('management:view'), async (req, res) => {
648
752
  try {
649
753
  const botId = parseInt(req.params.botId, 10);
650
754
  if (isNaN(botId)) return res.status(400).json({ error: 'Неверный ID бота' });
@@ -1007,7 +1111,7 @@ router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
1007
1111
  }
1008
1112
  });
1009
1113
 
1010
- router.get('/:id/settings/all', authorize('bot:update'), async (req, res) => {
1114
+ router.get('/:id/settings/all', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
1011
1115
  try {
1012
1116
  const botId = parseInt(req.params.id, 10);
1013
1117
 
@@ -1086,7 +1190,7 @@ router.get('/:id/settings/all', authorize('bot:update'), async (req, res) => {
1086
1190
 
1087
1191
  const nodeRegistry = require('../../core/NodeRegistry');
1088
1192
 
1089
- router.get('/:botId/visual-editor/nodes', authorize('management:view'), (req, res) => {
1193
+ router.get('/:botId/visual-editor/nodes', authenticate, checkBotAccess, authorize('management:view'), (req, res) => {
1090
1194
  try {
1091
1195
  const { graphType } = req.query;
1092
1196
  const nodesByCategory = nodeRegistry.getNodesByCategory(graphType);
@@ -1097,7 +1201,7 @@ router.get('/:botId/visual-editor/nodes', authorize('management:view'), (req, re
1097
1201
  }
1098
1202
  });
1099
1203
 
1100
- router.get('/:botId/visual-editor/node-config', authorize('management:view'), (req, res) => {
1204
+ router.get('/:botId/visual-editor/node-config', authenticate, checkBotAccess, authorize('management:view'), (req, res) => {
1101
1205
  try {
1102
1206
  const { types } = req.query;
1103
1207
  if (!types) {
@@ -1112,7 +1216,7 @@ router.get('/:botId/visual-editor/node-config', authorize('management:view'), (r
1112
1216
  }
1113
1217
  });
1114
1218
 
1115
- router.get('/:botId/visual-editor/permissions', authorize('management:view'), async (req, res) => {
1219
+ router.get('/:botId/visual-editor/permissions', authenticate, checkBotAccess, authorize('management:view'), async (req, res) => {
1116
1220
  try {
1117
1221
  const botId = parseInt(req.params.botId, 10);
1118
1222
  const permissions = await prisma.permission.findMany({
@@ -1733,7 +1837,7 @@ router.post('/:botId/plugins/:pluginName/action', authorize('plugin:list'), asyn
1733
1837
  });
1734
1838
 
1735
1839
 
1736
- router.get('/:botId/export', authorize('bot:export'), async (req, res) => {
1840
+ router.get('/:botId/export', authenticate, checkBotAccess, authorize('bot:export'), async (req, res) => {
1737
1841
  try {
1738
1842
  const botId = parseInt(req.params.botId, 10);
1739
1843
  const {
@@ -1964,7 +2068,18 @@ router.post('/import', authorize('bot:create'), upload.single('file'), async (re
1964
2068
  }
1965
2069
  }
1966
2070
 
1967
- const newPlugin = await prisma.installedPlugin.create({ data: pluginData });
2071
+ try {
2072
+ await pluginManager._installDependencies(newPluginPath);
2073
+ } catch (e) {
2074
+ console.warn(`[Import] Не удалось установить зависимости для плагина ${pluginName}: ${e.message}`);
2075
+ }
2076
+
2077
+ let newPlugin;
2078
+ try {
2079
+ newPlugin = await pluginManager.registerPlugin(newBot.id, newPluginPath, 'LOCAL', newPluginPath);
2080
+ } catch (e) {
2081
+ newPlugin = await prisma.installedPlugin.create({ data: pluginData });
2082
+ }
1968
2083
  pluginMap.set(oldPluginId, newPlugin.id);
1969
2084
  }
1970
2085
  }