blockmine 1.18.3 → 1.19.0

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 (28) hide show
  1. package/CHANGELOG.md +144 -107
  2. package/backend/cli.js +59 -57
  3. package/backend/prisma/migrations/20250701190321_add/migration.sql +2 -2
  4. package/backend/prisma/migrations/20250709150611_add_run_on_startup_to_tasks/migration.sql +21 -21
  5. package/backend/prisma/migrations/20250709151124_make_cron_pattern_optional/migration.sql +21 -21
  6. package/backend/prisma/migrations/20250718181335_add_plugin_data_store/migration.sql +14 -14
  7. package/backend/prisma/migrations/20250719115906_add_plugin_owner_to_graphs/migration.sql +45 -45
  8. package/backend/prisma/migrations/20250723160648_add_bot_sort_order/migration.sql +2 -2
  9. package/backend/prisma/migrations/20250816083216_add_panel_user_bot_access/migration.sql +30 -0
  10. package/backend/prisma/migrations/migration_lock.toml +2 -2
  11. package/backend/prisma/schema.prisma +244 -229
  12. package/backend/src/api/middleware/botAccess.js +35 -0
  13. package/backend/src/api/routes/auth.js +633 -595
  14. package/backend/src/api/routes/bots.js +292 -68
  15. package/backend/src/api/routes/eventGraphs.js +459 -459
  16. package/backend/src/api/routes/servers.js +27 -0
  17. package/backend/src/core/GraphExecutionEngine.js +917 -917
  18. package/backend/src/core/PluginLoader.js +208 -86
  19. package/backend/src/core/PluginManager.js +465 -427
  20. package/backend/src/core/commands/dev.js +6 -1
  21. package/backend/src/real-time/presence.js +74 -0
  22. package/backend/src/real-time/socketHandler.js +2 -0
  23. package/backend/src/server.js +193 -186
  24. package/frontend/dist/assets/{index-BqqUSU9S.js → index-5m_JZxJ-.js} +1693 -1688
  25. package/frontend/dist/assets/index-BFd7YoAj.css +1 -0
  26. package/frontend/dist/index.html +2 -2
  27. package/package.json +1 -1
  28. package/frontend/dist/assets/index-THQP1_d3.css +0 -1
@@ -52,7 +52,12 @@ class DevCommand extends Command {
52
52
  }
53
53
 
54
54
  const enabledPluginsCount = bot.config.plugins.length;
55
- const totalCommandsCount = bot.commands.size;
55
+
56
+ const uniqueCommands = new Set();
57
+ for (const command of bot.commands.values()) {
58
+ uniqueCommands.add(command.name);
59
+ }
60
+ const totalCommandsCount = uniqueCommands.size;
56
61
 
57
62
  bot.api.sendMessage(typeChat, `Бот создан с помощью - BlockMine. Версия: v${appVersion}. Активных плагинов: ${enabledPluginsCount}. Всего команд: ${totalCommandsCount}`, user.username);
58
63
  }
@@ -0,0 +1,74 @@
1
+ const jwt = require('jsonwebtoken');
2
+ const config = require('../config');
3
+
4
+ const JWT_SECRET = config.security.jwtSecret;
5
+
6
+ const presenceMap = new Map();
7
+
8
+ const HEARTBEAT_TTL_MS = 60 * 1000;
9
+
10
+ function verifyToken(token) {
11
+ try {
12
+ return jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] });
13
+ } catch (e) {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ function broadcast(io) {
19
+ const now = Date.now();
20
+ for (const [userId, info] of presenceMap.entries()) {
21
+ if (now - info.lastSeen > HEARTBEAT_TTL_MS) {
22
+ presenceMap.delete(userId);
23
+ }
24
+ }
25
+ const list = Array.from(presenceMap.entries()).map(([userId, info]) => ({
26
+ userId: Number(userId),
27
+ username: info.username,
28
+ path: info.path || '/',
29
+ lastSeen: info.lastSeen,
30
+ }));
31
+ io.emit('presence:list', list);
32
+ }
33
+
34
+ function handleConnection(io, socket) {
35
+ const token = socket.handshake?.auth?.token;
36
+ const decoded = verifyToken(token);
37
+ if (!decoded) {
38
+ return socket.disconnect(true);
39
+ }
40
+ const { userId, username } = decoded;
41
+ presenceMap.set(userId, { username, socketId: socket.id, lastSeen: Date.now(), path: '/' });
42
+ broadcast(io);
43
+
44
+ socket.on('presence:heartbeat', () => {
45
+ const info = presenceMap.get(userId);
46
+ if (info) {
47
+ info.lastSeen = Date.now();
48
+ presenceMap.set(userId, info);
49
+ broadcast(io);
50
+ }
51
+ });
52
+
53
+ socket.on('presence:update', ({ path }) => {
54
+ const info = presenceMap.get(userId) || { username, socketId: socket.id };
55
+ info.lastSeen = Date.now();
56
+ info.path = typeof path === 'string' ? path : '/';
57
+ presenceMap.set(userId, info);
58
+ broadcast(io);
59
+ });
60
+
61
+ socket.on('disconnect', () => {
62
+ for (const [uid, info] of presenceMap.entries()) {
63
+ if (info.socketId === socket.id) {
64
+ presenceMap.delete(uid);
65
+ }
66
+ }
67
+ broadcast(io);
68
+ });
69
+ }
70
+
71
+ module.exports = {
72
+ handleConnection,
73
+ broadcast,
74
+ };
@@ -2,6 +2,7 @@ const { Server } = require('socket.io');
2
2
  const config = require('../config');
3
3
 
4
4
  const { botManager } = require('../core/services');
5
+ const presence = require('./presence');
5
6
 
6
7
  let io;
7
8
 
@@ -18,6 +19,7 @@ function initializeSocket(httpServer) {
18
19
  });
19
20
 
20
21
  io.on('connection', (socket) => {
22
+ presence.handleConnection(io, socket);
21
23
 
22
24
  socket.on('disconnect', () => {
23
25
  botManager.handleSocketDisconnect(socket);
@@ -1,187 +1,194 @@
1
- const express = require('express');
2
- const http = require('http');
3
- const path = require('path');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const prisma = require('./lib/prisma');
7
-
8
- const config = require('./config');
9
- const { initializeSocket } = require('./real-time/socketHandler');
10
- const { botManager, pluginManager } = require('./core/services');
11
-
12
- const botRoutes = require('./api/routes/bots');
13
- const pluginRoutes = require('./api/routes/plugins');
14
- const serverRoutes = require('./api/routes/servers');
15
- const permissionsRoutes = require('./api/routes/permissions');
16
- const taskRoutes = require('./api/routes/tasks');
17
- const { router: authRoutes, ALL_PERMISSIONS } = require('./api/routes/auth');
18
- const searchRoutes = require('./api/routes/search');
19
- const eventGraphsRouter = require('./api/routes/eventGraphs');
20
- const TaskScheduler = require('./core/TaskScheduler');
21
- const panelRoutes = require('./api/routes/panel');
22
- const changelogRoutes = require('./api/routes/changelog');
23
- const logsRoutes = require('./api/routes/logs');
24
-
25
- const app = express();
26
- const server = http.createServer(app);
27
-
28
- initializeSocket(server);
29
-
30
- app.set('botManager', botManager);
31
- app.set('pluginManager', pluginManager);
32
-
33
- const PORT = config.server.port;
34
- const HOST = config.server.host;
35
-
36
- app.use(express.json({ limit: '50mb' }));
37
- app.use(express.urlencoded({ limit: '50mb', extended: true }));
38
-
39
- const frontendPath = path.resolve(__dirname, '..', '..', 'frontend', 'dist');
40
- const rootPath = path.resolve(__dirname, '..', '..');
41
-
42
- app.use('/api/auth', authRoutes);
43
-
44
- app.use('/api/version', (req, res, next) => {
45
- async function getVersion() {
46
- try {
47
- const packageJsonPath = path.join(rootPath, 'package.json');
48
- const packageJsonData = await fs.promises.readFile(packageJsonPath, 'utf-8');
49
- const { version } = JSON.parse(packageJsonData);
50
- res.json({ version });
51
- } catch (error) {
52
- console.error("Failed to read app version:", error);
53
- res.status(500).json({ error: 'Could not retrieve app version.' });
54
- }
55
- }
56
- getVersion();
57
- });
58
- app.use('/api/tasks', taskRoutes);
59
- app.use('/api/bots', botRoutes);
60
- app.use('/api/plugins', pluginRoutes);
61
- app.use('/api/servers', serverRoutes);
62
- app.use('/api/permissions', permissionsRoutes);
63
- app.use('/api/search', searchRoutes);
64
- app.use('/api/panel', panelRoutes);
65
- app.use('/api/changelog', changelogRoutes);
66
- app.use('/api/logs', logsRoutes);
67
-
68
- app.use(express.static(frontendPath));
69
-
70
- app.get(/^(?!\/api).*/, (req, res) => {
71
- const indexPath = path.join(frontendPath, 'index.html');
72
-
73
- if (fs.existsSync(indexPath)) {
74
- res.sendFile(indexPath);
75
- } else {
76
- console.error(`Критическая ошибка: файл index.html не найден по пути ${indexPath}`);
77
- res.status(404).send(
78
- '<h1>Файлы фронтенда не найдены! Соберите фронтенд командой "npm run build --workspace=frontend"</h1>'
79
- );
80
- }
81
- });
82
-
83
- async function runStartupMigrations() {
84
- try {
85
- const adminRole = await prisma.panelRole.findUnique({ where: { name: 'Admin' } });
86
- if (adminRole) {
87
- const permissions = JSON.parse(adminRole.permissions);
88
- if (permissions.includes('*')) {
89
- const newPermissions = ALL_PERMISSIONS
90
- .map(p => p.id)
91
- .filter(id => id !== '*');
92
-
93
- await prisma.panelRole.update({
94
- where: { id: adminRole.id },
95
- data: { permissions: JSON.stringify(newPermissions) }
96
- });
97
- }
98
- }
99
-
100
- const rootUser = await prisma.panelUser.findUnique({ where: { id: 1 }, include: { role: true } });
101
- if (rootUser && rootUser.role) {
102
- const allPermissions = ALL_PERMISSIONS.map(p => p.id).filter(id => id !== '*');
103
- const currentPermissions = JSON.parse(rootUser.role.permissions);
104
-
105
- if (JSON.stringify(allPermissions.sort()) !== JSON.stringify(currentPermissions.sort())) {
106
- await prisma.panelRole.update({
107
- where: { id: rootUser.role.id },
108
- data: { permissions: JSON.stringify(allPermissions) }
109
- });
110
- console.log(`[Migration] Права для root-пользователя "${rootUser.username}" (ID: 1) были синхронизированы.`);
111
- }
112
- }
113
- } catch (error) {
114
- console.error('[Migration] Ошибка во время миграции прав:', error);
115
- }
116
- }
117
-
118
- async function startServer() {
119
- await runStartupMigrations();
120
- return new Promise((resolve) => {
121
- server.listen(PORT, HOST, async () => {
122
- console.log(`\nBackend сервер успешно запущен на http://${HOST}:${PORT}`);
123
-
124
- if (HOST === '0.0.0.0') {
125
- const networkInterfaces = os.networkInterfaces();
126
- console.log('Панель управления доступна по следующим адресам:');
127
- console.log(` - Локально: http://localhost:${PORT}`);
128
- Object.keys(networkInterfaces).forEach(ifaceName => {
129
- networkInterfaces[ifaceName].forEach(iface => {
130
- if (iface.family === 'IPv4' && !iface.internal) {
131
- console.log(` - В сети: http://${iface.address}:${PORT}`);
132
- }
133
- });
134
- });
135
- console.log(' - А также по вашему внешнему IP адресу.');
136
-
137
- } else {
138
- console.log(`Панель управления доступна по адресу: http://localhost:${PORT}`);
139
- }
140
-
141
- await TaskScheduler.initialize();
142
- resolve(server);
143
- });
144
- });
145
- }
146
-
147
-
148
- const gracefulShutdown = async (signal) => {
149
- console.log(`[Shutdown] Получен сигнал ${signal}. Начинаем корректное завершение...`);
150
-
151
- TaskScheduler.shutdown();
152
-
153
- const botIds = Array.from(botManager.bots.keys());
154
- if (botIds.length > 0) {
155
- console.log(`[Shutdown] Остановка ${botIds.length} активных ботов...`);
156
- await Promise.all(botIds.map(botId => botManager.stopBot(botId)));
157
- console.log('[Shutdown] Все боты остановлены.');
158
- }
159
-
160
- const io = require('./real-time/socketHandler').getIO();
161
- if (io) {
162
- io.close(async () => {
163
- console.log('[Shutdown] WebSocket сервер закрыт.');
164
-
165
- await new Promise(resolve => server.close(resolve));
166
- console.log('[Shutdown] HTTP сервер закрыт.');
167
-
168
- const prisma = require('./lib/prisma');
169
- await prisma.$disconnect();
170
- console.log('[Shutdown] Соединение с БД закрыто.');
171
-
172
- console.log('[Shutdown] Корректное завершение выполнено.');
173
- process.exit(0);
174
- });
175
- }
176
- };
177
-
178
- process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2 (nodemon)'));
179
- process.on('SIGINT', () => gracefulShutdown('SIGINT (Ctrl+C)'));
180
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
181
-
182
-
183
- module.exports = { startServer, app, server };
184
-
185
- if (require.main === module) {
186
- startServer();
1
+ const express = require('express');
2
+ const http = require('http');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const prisma = require('./lib/prisma');
7
+
8
+ const config = require('./config');
9
+ const { initializeSocket } = require('./real-time/socketHandler');
10
+ const { botManager, pluginManager } = require('./core/services');
11
+
12
+ const botRoutes = require('./api/routes/bots');
13
+ const pluginRoutes = require('./api/routes/plugins');
14
+ const serverRoutes = require('./api/routes/servers');
15
+ const permissionsRoutes = require('./api/routes/permissions');
16
+ const taskRoutes = require('./api/routes/tasks');
17
+ const { router: authRoutes, ALL_PERMISSIONS, VIEWER_PERMISSIONS } = require('./api/routes/auth');
18
+ const searchRoutes = require('./api/routes/search');
19
+ const eventGraphsRouter = require('./api/routes/eventGraphs');
20
+ const TaskScheduler = require('./core/TaskScheduler');
21
+ const panelRoutes = require('./api/routes/panel');
22
+ const changelogRoutes = require('./api/routes/changelog');
23
+ const logsRoutes = require('./api/routes/logs');
24
+
25
+ const app = express();
26
+ const server = http.createServer(app);
27
+
28
+ initializeSocket(server);
29
+
30
+ app.set('botManager', botManager);
31
+ app.set('pluginManager', pluginManager);
32
+
33
+ const PORT = config.server.port;
34
+ const HOST = config.server.host;
35
+
36
+ app.use(express.json({ limit: '50mb' }));
37
+ app.use(express.urlencoded({ limit: '50mb', extended: true }));
38
+
39
+ const frontendPath = path.resolve(__dirname, '..', '..', 'frontend', 'dist');
40
+ const rootPath = path.resolve(__dirname, '..', '..');
41
+
42
+ app.use('/api/auth', authRoutes);
43
+
44
+ app.use('/api/version', (req, res, next) => {
45
+ async function getVersion() {
46
+ try {
47
+ const packageJsonPath = path.join(rootPath, 'package.json');
48
+ const packageJsonData = await fs.promises.readFile(packageJsonPath, 'utf-8');
49
+ const { version } = JSON.parse(packageJsonData);
50
+ res.json({ version });
51
+ } catch (error) {
52
+ console.error("Failed to read app version:", error);
53
+ res.status(500).json({ error: 'Could not retrieve app version.' });
54
+ }
55
+ }
56
+ getVersion();
57
+ });
58
+ app.use('/api/tasks', taskRoutes);
59
+ app.use('/api/bots', botRoutes);
60
+ app.use('/api/plugins', pluginRoutes);
61
+ app.use('/api/servers', serverRoutes);
62
+ app.use('/api/permissions', permissionsRoutes);
63
+ app.use('/api/search', searchRoutes);
64
+ app.use('/api/panel', panelRoutes);
65
+ app.use('/api/changelog', changelogRoutes);
66
+ app.use('/api/logs', logsRoutes);
67
+
68
+ app.use(express.static(frontendPath));
69
+
70
+ app.get(/^(?!\/api).*/, (req, res) => {
71
+ const indexPath = path.join(frontendPath, 'index.html');
72
+
73
+ if (fs.existsSync(indexPath)) {
74
+ res.sendFile(indexPath);
75
+ } else {
76
+ console.error(`Критическая ошибка: файл index.html не найден по пути ${indexPath}`);
77
+ res.status(404).send(
78
+ '<h1>Файлы фронтенда не найдены! Соберите фронтенд командой "npm run build --workspace=frontend"</h1>'
79
+ );
80
+ }
81
+ });
82
+
83
+ async function runStartupMigrations() {
84
+ try {
85
+ const adminRole = await prisma.panelRole.findUnique({ where: { name: 'Admin' } });
86
+ if (adminRole) {
87
+ const permissions = JSON.parse(adminRole.permissions);
88
+ if (permissions.includes('*')) {
89
+ const newPermissions = ALL_PERMISSIONS
90
+ .map(p => p.id)
91
+ .filter(id => id !== '*');
92
+
93
+ await prisma.panelRole.update({
94
+ where: { id: adminRole.id },
95
+ data: { permissions: JSON.stringify(newPermissions) }
96
+ });
97
+ }
98
+ }
99
+
100
+ // Создаем/обновляем роль Viewer
101
+ const viewerRole = await prisma.panelRole.upsert({
102
+ where: { name: 'Viewer' },
103
+ update: { permissions: JSON.stringify(VIEWER_PERMISSIONS) },
104
+ create: { name: 'Viewer', permissions: JSON.stringify(VIEWER_PERMISSIONS) }
105
+ });
106
+
107
+ const rootUser = await prisma.panelUser.findUnique({ where: { id: 1 }, include: { role: true } });
108
+ if (rootUser && rootUser.role) {
109
+ const allPermissions = ALL_PERMISSIONS.map(p => p.id).filter(id => id !== '*');
110
+ const currentPermissions = JSON.parse(rootUser.role.permissions);
111
+
112
+ if (JSON.stringify(allPermissions.sort()) !== JSON.stringify(currentPermissions.sort())) {
113
+ await prisma.panelRole.update({
114
+ where: { id: rootUser.role.id },
115
+ data: { permissions: JSON.stringify(allPermissions) }
116
+ });
117
+ console.log(`[Migration] Права для root-пользователя "${rootUser.username}" (ID: 1) были синхронизированы.`);
118
+ }
119
+ }
120
+ } catch (error) {
121
+ console.error('[Migration] Ошибка во время миграции прав:', error);
122
+ }
123
+ }
124
+
125
+ async function startServer() {
126
+ await runStartupMigrations();
127
+ return new Promise((resolve) => {
128
+ server.listen(PORT, HOST, async () => {
129
+ console.log(`\nBackend сервер успешно запущен на http://${HOST}:${PORT}`);
130
+
131
+ if (HOST === '0.0.0.0') {
132
+ const networkInterfaces = os.networkInterfaces();
133
+ console.log('Панель управления доступна по следующим адресам:');
134
+ console.log(` - Локально: http://localhost:${PORT}`);
135
+ Object.keys(networkInterfaces).forEach(ifaceName => {
136
+ networkInterfaces[ifaceName].forEach(iface => {
137
+ if (iface.family === 'IPv4' && !iface.internal) {
138
+ console.log(` - В сети: http://${iface.address}:${PORT}`);
139
+ }
140
+ });
141
+ });
142
+ console.log(' - А также по вашему внешнему IP адресу.');
143
+
144
+ } else {
145
+ console.log(`Панель управления доступна по адресу: http://localhost:${PORT}`);
146
+ }
147
+
148
+ await TaskScheduler.initialize();
149
+ resolve(server);
150
+ });
151
+ });
152
+ }
153
+
154
+
155
+ const gracefulShutdown = async (signal) => {
156
+ console.log(`[Shutdown] Получен сигнал ${signal}. Начинаем корректное завершение...`);
157
+
158
+ TaskScheduler.shutdown();
159
+
160
+ const botIds = Array.from(botManager.bots.keys());
161
+ if (botIds.length > 0) {
162
+ console.log(`[Shutdown] Остановка ${botIds.length} активных ботов...`);
163
+ await Promise.all(botIds.map(botId => botManager.stopBot(botId)));
164
+ console.log('[Shutdown] Все боты остановлены.');
165
+ }
166
+
167
+ const io = require('./real-time/socketHandler').getIO();
168
+ if (io) {
169
+ io.close(async () => {
170
+ console.log('[Shutdown] WebSocket сервер закрыт.');
171
+
172
+ await new Promise(resolve => server.close(resolve));
173
+ console.log('[Shutdown] HTTP сервер закрыт.');
174
+
175
+ const prisma = require('./lib/prisma');
176
+ await prisma.$disconnect();
177
+ console.log('[Shutdown] Соединение с БД закрыто.');
178
+
179
+ console.log('[Shutdown] Корректное завершение выполнено.');
180
+ process.exit(0);
181
+ });
182
+ }
183
+ };
184
+
185
+ process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2 (nodemon)'));
186
+ process.on('SIGINT', () => gracefulShutdown('SIGINT (Ctrl+C)'));
187
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
188
+
189
+
190
+ module.exports = { startServer, app, server };
191
+
192
+ if (require.main === module) {
193
+ startServer();
187
194
  }