blockmine 1.16.3 → 1.17.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.
@@ -1,459 +1,459 @@
1
- const express = require('express');
2
- const { param, body, validationResult } = require('express-validator');
3
- const { PrismaClient } = require('@prisma/client');
4
- const { authorize } = require('../middleware/auth');
5
- const logger = require('../../lib/logger');
6
- const crypto = require('crypto');
7
- const fse = require('fs-extra');
8
- const path = require('path');
9
-
10
- const prisma = new PrismaClient();
11
- const router = express.Router({ mergeParams: true });
12
-
13
- router.get('/',
14
- authorize('management:view'),
15
- async (req, res) => {
16
- const { botId } = req.params;
17
- const eventGraphs = await prisma.eventGraph.findMany({
18
- where: { botId: parseInt(botId) },
19
- include: {
20
- triggers: true,
21
- pluginOwner: {
22
- select: {
23
- id: true,
24
- name: true,
25
- version: true,
26
- sourceType: true
27
- }
28
- }
29
- },
30
- orderBy: { createdAt: 'desc' }
31
- });
32
-
33
- const graphsWithCounts = eventGraphs.map(graph => {
34
- let nodeCount = 0;
35
- let edgeCount = 0;
36
- if (graph.graphJson) {
37
- try {
38
- const parsedGraph = JSON.parse(graph.graphJson);
39
- nodeCount = parsedGraph.nodes?.length || 0;
40
- edgeCount = parsedGraph.connections?.length || 0;
41
- } catch (e) {
42
- }
43
- }
44
- return { ...graph, nodeCount, edgeCount };
45
- });
46
-
47
- res.json(graphsWithCounts);
48
- }
49
- );
50
-
51
- router.post('/',
52
- authorize('management:edit'),
53
- [body('name').isString().notEmpty()],
54
- async (req, res) => {
55
- const errors = validationResult(req);
56
- if (!errors.isEmpty()) {
57
- return res.status(400).json({ errors: errors.array() });
58
- }
59
-
60
- const { botId } = req.params;
61
- const { name, graphJson, variables, isEnabled = true } = req.body;
62
-
63
- let graphJsonString;
64
- if (graphJson) {
65
- if (typeof graphJson === 'string') {
66
- graphJsonString = graphJson;
67
- } else {
68
- graphJsonString = JSON.stringify(graphJson);
69
- }
70
- } else {
71
- graphJsonString = JSON.stringify({
72
- nodes: [],
73
- connections: []
74
- });
75
- }
76
-
77
- try {
78
- let eventTypes = [];
79
- try {
80
- const parsedGraph = JSON.parse(graphJsonString);
81
- if (parsedGraph.nodes && Array.isArray(parsedGraph.nodes)) {
82
- const eventNodes = parsedGraph.nodes.filter(node => node.type && node.type.startsWith('event:'));
83
- eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
84
- }
85
- } catch (error) {
86
- console.warn('[API] Не удалось извлечь типы событий из графа:', error.message);
87
- }
88
-
89
- const newGraph = await prisma.eventGraph.create({
90
- data: {
91
- botId: parseInt(botId),
92
- name: name.trim(),
93
- isEnabled: isEnabled,
94
- graphJson: graphJsonString,
95
- variables: variables || '[]',
96
- pluginOwnerId: req.body.pluginOwnerId || null,
97
- triggers: {
98
- create: eventTypes.map(eventType => ({ eventType }))
99
- }
100
- },
101
- include: { triggers: true }
102
- });
103
-
104
- res.status(201).json(newGraph);
105
- } catch (error) {
106
- if (error.code === 'P2002') {
107
- return res.status(409).json({ error: `Event graph with name "${name}" already exists.` });
108
- }
109
- console.error("Failed to create event graph:", error);
110
- res.status(500).json({ error: 'Failed to create event graph' });
111
- }
112
- }
113
- );
114
-
115
- router.get('/:graphId',
116
- authorize('management:view'),
117
- [param('graphId').isInt()],
118
- async (req, res) => {
119
- const errors = validationResult(req);
120
- if (!errors.isEmpty()) {
121
- return res.status(400).json({ errors: errors.array() });
122
- }
123
-
124
- const { graphId } = req.params;
125
- const eventGraph = await prisma.eventGraph.findUnique({
126
- where: { id: parseInt(graphId) },
127
- include: {
128
- triggers: true,
129
- pluginOwner: {
130
- select: {
131
- id: true,
132
- name: true,
133
- version: true,
134
- sourceType: true
135
- }
136
- }
137
- }
138
- });
139
-
140
-
141
- if (!eventGraph) {
142
- return res.status(404).json({ error: 'Event graph not found' });
143
- }
144
- res.json(eventGraph);
145
- }
146
- );
147
-
148
- router.put('/:graphId',
149
- authorize('management:edit'),
150
- [
151
- param('graphId').isInt(),
152
- body('name').optional().isString().notEmpty(),
153
- body('isEnabled').optional().isBoolean(),
154
- body('graphJson').optional().isJSON(),
155
- body('variables').optional().isArray()
156
- ],
157
- async (req, res) => {
158
- const errors = validationResult(req);
159
- if (!errors.isEmpty()) {
160
- return res.status(400).json({ errors: errors.array() });
161
- }
162
-
163
- const { graphId } = req.params;
164
- const { name, isEnabled, graphJson, variables, pluginOwnerId } = req.body;
165
-
166
-
167
-
168
- try {
169
- const dataToUpdate = {};
170
- if (name !== undefined) dataToUpdate.name = name;
171
- if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
172
- if (pluginOwnerId !== undefined) dataToUpdate.pluginOwnerId = pluginOwnerId;
173
-
174
- if (graphJson !== undefined) {
175
- dataToUpdate.graphJson = graphJson;
176
-
177
- const parsedGraph = JSON.parse(graphJson);
178
- const eventNodes = parsedGraph.nodes.filter(node => node.type.startsWith('event:'));
179
- const eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
180
-
181
- const existingGraph = await prisma.eventGraph.findUnique({
182
- where: { id: parseInt(graphId) },
183
- include: { triggers: true }
184
- });
185
-
186
- const existingEventTypes = existingGraph?.triggers?.map(t => t.eventType) || [];
187
-
188
- const eventTypesToDelete = existingEventTypes.filter(et => !eventTypes.includes(et));
189
- const eventTypesToCreate = eventTypes.filter(et => !existingEventTypes.includes(et));
190
-
191
- dataToUpdate.triggers = {
192
- deleteMany: {},
193
- create: eventTypes.map(eventType => ({ eventType })),
194
- };
195
- }
196
-
197
- if (variables !== undefined) {
198
- dataToUpdate.variables = Array.isArray(variables) ? JSON.stringify(variables) : variables;
199
- }
200
-
201
- const updatedGraph = await prisma.eventGraph.update({
202
- where: { id: parseInt(graphId) },
203
- data: dataToUpdate,
204
- include: { triggers: true },
205
- });
206
-
207
- if (graphJson && updatedGraph.pluginOwnerId) {
208
- try {
209
- const plugin = await prisma.installedPlugin.findUnique({
210
- where: { id: updatedGraph.pluginOwnerId }
211
- });
212
-
213
- if (plugin) {
214
- const graphDir = path.join(plugin.path, 'graph');
215
- await fse.mkdir(graphDir, { recursive: true });
216
-
217
- const graphFile = path.join(graphDir, `${updatedGraph.name}.json`);
218
- await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
219
- console.log(`[API] Граф события ${updatedGraph.name} сохранен в ${graphFile}`);
220
- }
221
- } catch (error) {
222
- console.error(`[API] Ошибка сохранения графа события в папку плагина:`, error);
223
- }
224
- }
225
-
226
- res.json(updatedGraph);
227
- } catch (error) {
228
- logger.error(error, "--- ERROR CAUGHT IN EVENT GRAPH UPDATE ---");
229
- res.status(500).json({ error: 'Failed to update event graph' });
230
- }
231
- }
232
- );
233
-
234
- router.delete('/:graphId',
235
- authorize('management:edit'),
236
- [param('graphId').isInt()],
237
- async (req, res) => {
238
- const errors = validationResult(req);
239
- if (!errors.isEmpty()) {
240
- return res.status(400).json({ errors: errors.array() });
241
- }
242
- const { graphId } = req.params;
243
- try {
244
- await prisma.eventGraph.delete({
245
- where: { id: parseInt(graphId) },
246
- });
247
- res.status(204).send();
248
- } catch (error) {
249
- console.error('Failed to delete event graph:', error);
250
- res.status(500).json({ error: 'Failed to delete event graph' });
251
- }
252
- }
253
- );
254
-
255
- router.get('/:graphId/export',
256
- authorize('management:view'),
257
- [param('graphId').isInt()],
258
- async (req, res) => {
259
- const errors = validationResult(req);
260
- if (!errors.isEmpty()) {
261
- return res.status(400).json({ errors: errors.array() });
262
- }
263
-
264
- const { graphId } = req.params;
265
- try {
266
- const eventGraph = await prisma.eventGraph.findUnique({
267
- where: { id: parseInt(graphId) },
268
- include: { triggers: true },
269
- });
270
-
271
- if (!eventGraph) {
272
- return res.status(404).json({ error: 'Event graph not found' });
273
- }
274
-
275
- const exportData = {
276
- name: eventGraph.name,
277
- graphJson: eventGraph.graphJson,
278
- variables: eventGraph.variables,
279
- triggers: eventGraph.triggers.map(t => t.eventType),
280
- };
281
-
282
- res.json(exportData);
283
- } catch (error) {
284
- console.error('Failed to export event graph:', error);
285
- res.status(500).json({ error: 'Failed to export event graph' });
286
- }
287
- }
288
- );
289
-
290
- router.post('/import',
291
- authorize('management:edit'),
292
- [
293
- body('name').isString().notEmpty(),
294
- body('graphJson').isJSON(),
295
- body('variables').optional().isJSON(),
296
- ],
297
- async (req, res) => {
298
- const errors = validationResult(req);
299
- if (!errors.isEmpty()) {
300
- return res.status(400).json({ errors: errors.array() });
301
- }
302
-
303
- const { botId } = req.params;
304
- const { name, graphJson, variables } = req.body;
305
-
306
- try {
307
- const graph = JSON.parse(graphJson);
308
- const idMap = new Map();
309
-
310
- if (graph.nodes && Array.isArray(graph.nodes)) {
311
- graph.nodes.forEach(node => {
312
- if (node && node.id) {
313
- const oldId = node.id;
314
- const newId = `node_${crypto.randomUUID()}`;
315
- idMap.set(oldId, newId);
316
- node.id = newId;
317
- }
318
- });
319
- }
320
-
321
- const connectionList = graph.edges || graph.connections;
322
- const updatedConnections = [];
323
- if (connectionList && Array.isArray(connectionList)) {
324
- connectionList.forEach(conn => {
325
- if (conn && (conn.source || conn.sourceNodeId) && (conn.target || conn.targetNodeId)) {
326
- const oldSourceId = conn.source || conn.sourceNodeId;
327
- const oldTargetId = conn.target || conn.targetNodeId;
328
-
329
- const newSourceId = idMap.get(oldSourceId);
330
- const newTargetId = idMap.get(oldTargetId);
331
-
332
- if (newSourceId && newTargetId) {
333
- conn.id = `edge_${crypto.randomUUID()}`;
334
- conn.sourceNodeId = newSourceId;
335
- conn.targetNodeId = newTargetId;
336
-
337
- delete conn.source;
338
- delete conn.target;
339
-
340
- updatedConnections.push(conn);
341
- }
342
- }
343
- });
344
-
345
- graph.connections = updatedConnections;
346
- delete graph.edges;
347
- }
348
-
349
- const newGraphJson = JSON.stringify(graph);
350
-
351
- const eventNodes = graph.nodes.filter(node => node.type.startsWith('event:'));
352
- const eventTypes = eventNodes.map(node => node.type.split(':')[1]);
353
-
354
- const newGraph = await prisma.eventGraph.create({
355
- data: {
356
- botId: parseInt(botId),
357
- name: `${name} (копия)`,
358
- graphJson: newGraphJson,
359
- variables: variables || '[]',
360
- isEnabled: false,
361
- triggers: {
362
- create: eventTypes.map(eventType => ({ eventType })),
363
- },
364
- },
365
- include: { triggers: true }
366
- });
367
- res.status(201).json(newGraph);
368
- } catch (error) {
369
- if (error.code === 'P2002') {
370
- return res.status(409).json({ error: `Event graph with name "${name} (копия)" already exists.` });
371
- }
372
- console.error("Failed to import event graph:", error);
373
- res.status(500).json({ error: 'Failed to import event graph' });
374
- }
375
- }
376
- );
377
-
378
- router.post('/:graphId/duplicate',
379
- authorize('management:edit'),
380
- [param('graphId').isInt()],
381
- async (req, res) => {
382
- const errors = validationResult(req);
383
- if (!errors.isEmpty()) {
384
- return res.status(400).json({ errors: errors.array() });
385
- }
386
-
387
- const { botId, graphId } = req.params;
388
-
389
- try {
390
- const originalGraph = await prisma.eventGraph.findUnique({
391
- where: { id: parseInt(graphId) },
392
- include: { triggers: true },
393
- });
394
-
395
- if (!originalGraph) {
396
- return res.status(404).json({ error: 'Original event graph not found' });
397
- }
398
-
399
- const graph = JSON.parse(originalGraph.graphJson);
400
- const idMap = new Map();
401
-
402
- if (graph.nodes && Array.isArray(graph.nodes)) {
403
- graph.nodes.forEach(node => {
404
- if (node && node.id) {
405
- const oldId = node.id;
406
- const newId = `node_${crypto.randomUUID()}`;
407
- idMap.set(oldId, newId);
408
- node.id = newId;
409
- }
410
- });
411
- }
412
-
413
- const connectionList = graph.connections || [];
414
- const updatedConnections = [];
415
- if (Array.isArray(connectionList)) {
416
- connectionList.forEach(conn => {
417
- if (conn && conn.sourceNodeId && conn.targetNodeId) {
418
- const newSourceId = idMap.get(conn.sourceNodeId);
419
- const newTargetId = idMap.get(conn.targetNodeId);
420
-
421
- if (newSourceId && newTargetId) {
422
- updatedConnections.push({
423
- ...conn,
424
- id: `edge_${crypto.randomUUID()}`,
425
- sourceNodeId: newSourceId,
426
- targetNodeId: newTargetId,
427
- });
428
- }
429
- }
430
- });
431
- }
432
- graph.connections = updatedConnections;
433
-
434
- const newGraphJson = JSON.stringify(graph);
435
- const newName = `${originalGraph.name} (копия)`;
436
-
437
- const newGraph = await prisma.eventGraph.create({
438
- data: {
439
- botId: parseInt(botId),
440
- name: newName,
441
- graphJson: newGraphJson,
442
- variables: originalGraph.variables,
443
- isEnabled: false,
444
- triggers: {
445
- create: originalGraph.triggers.map(t => ({ eventType: t.eventType })),
446
- },
447
- },
448
- });
449
-
450
- res.status(201).json(newGraph);
451
- } catch (error) {
452
- logger.error('Failed to duplicate event graph:', error);
453
- res.status(500).json({ error: 'Failed to duplicate event graph' });
454
- }
455
- }
456
- );
457
-
458
- module.exports = router;
459
-
1
+ const express = require('express');
2
+ const { param, body, validationResult } = require('express-validator');
3
+ const { PrismaClient } = require('@prisma/client');
4
+ const { authorize } = require('../middleware/auth');
5
+ const logger = require('../../lib/logger');
6
+ const crypto = require('crypto');
7
+ const fse = require('fs-extra');
8
+ const path = require('path');
9
+
10
+ const prisma = new PrismaClient();
11
+ const router = express.Router({ mergeParams: true });
12
+
13
+ router.get('/',
14
+ authorize('management:view'),
15
+ async (req, res) => {
16
+ const { botId } = req.params;
17
+ const eventGraphs = await prisma.eventGraph.findMany({
18
+ where: { botId: parseInt(botId) },
19
+ include: {
20
+ triggers: true,
21
+ pluginOwner: {
22
+ select: {
23
+ id: true,
24
+ name: true,
25
+ version: true,
26
+ sourceType: true
27
+ }
28
+ }
29
+ },
30
+ orderBy: { createdAt: 'desc' }
31
+ });
32
+
33
+ const graphsWithCounts = eventGraphs.map(graph => {
34
+ let nodeCount = 0;
35
+ let edgeCount = 0;
36
+ if (graph.graphJson) {
37
+ try {
38
+ const parsedGraph = JSON.parse(graph.graphJson);
39
+ nodeCount = parsedGraph.nodes?.length || 0;
40
+ edgeCount = parsedGraph.connections?.length || 0;
41
+ } catch (e) {
42
+ }
43
+ }
44
+ return { ...graph, nodeCount, edgeCount };
45
+ });
46
+
47
+ res.json(graphsWithCounts);
48
+ }
49
+ );
50
+
51
+ router.post('/',
52
+ authorize('management:edit'),
53
+ [body('name').isString().notEmpty()],
54
+ async (req, res) => {
55
+ const errors = validationResult(req);
56
+ if (!errors.isEmpty()) {
57
+ return res.status(400).json({ errors: errors.array() });
58
+ }
59
+
60
+ const { botId } = req.params;
61
+ const { name, graphJson, variables, isEnabled = true } = req.body;
62
+
63
+ let graphJsonString;
64
+ if (graphJson) {
65
+ if (typeof graphJson === 'string') {
66
+ graphJsonString = graphJson;
67
+ } else {
68
+ graphJsonString = JSON.stringify(graphJson);
69
+ }
70
+ } else {
71
+ graphJsonString = JSON.stringify({
72
+ nodes: [],
73
+ connections: []
74
+ });
75
+ }
76
+
77
+ try {
78
+ let eventTypes = [];
79
+ try {
80
+ const parsedGraph = JSON.parse(graphJsonString);
81
+ if (parsedGraph.nodes && Array.isArray(parsedGraph.nodes)) {
82
+ const eventNodes = parsedGraph.nodes.filter(node => node.type && node.type.startsWith('event:'));
83
+ eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
84
+ }
85
+ } catch (error) {
86
+ console.warn('[API] Не удалось извлечь типы событий из графа:', error.message);
87
+ }
88
+
89
+ const newGraph = await prisma.eventGraph.create({
90
+ data: {
91
+ botId: parseInt(botId),
92
+ name: name.trim(),
93
+ isEnabled: isEnabled,
94
+ graphJson: graphJsonString,
95
+ variables: variables || '[]',
96
+ pluginOwnerId: req.body.pluginOwnerId || null,
97
+ triggers: {
98
+ create: eventTypes.map(eventType => ({ eventType }))
99
+ }
100
+ },
101
+ include: { triggers: true }
102
+ });
103
+
104
+ res.status(201).json(newGraph);
105
+ } catch (error) {
106
+ if (error.code === 'P2002') {
107
+ return res.status(409).json({ error: `Event graph with name "${name}" already exists.` });
108
+ }
109
+ console.error("Failed to create event graph:", error);
110
+ res.status(500).json({ error: 'Failed to create event graph' });
111
+ }
112
+ }
113
+ );
114
+
115
+ router.get('/:graphId',
116
+ authorize('management:view'),
117
+ [param('graphId').isInt()],
118
+ async (req, res) => {
119
+ const errors = validationResult(req);
120
+ if (!errors.isEmpty()) {
121
+ return res.status(400).json({ errors: errors.array() });
122
+ }
123
+
124
+ const { graphId } = req.params;
125
+ const eventGraph = await prisma.eventGraph.findUnique({
126
+ where: { id: parseInt(graphId) },
127
+ include: {
128
+ triggers: true,
129
+ pluginOwner: {
130
+ select: {
131
+ id: true,
132
+ name: true,
133
+ version: true,
134
+ sourceType: true
135
+ }
136
+ }
137
+ }
138
+ });
139
+
140
+
141
+ if (!eventGraph) {
142
+ return res.status(404).json({ error: 'Event graph not found' });
143
+ }
144
+ res.json(eventGraph);
145
+ }
146
+ );
147
+
148
+ router.put('/:graphId',
149
+ authorize('management:edit'),
150
+ [
151
+ param('graphId').isInt(),
152
+ body('name').optional().isString().notEmpty(),
153
+ body('isEnabled').optional().isBoolean(),
154
+ body('graphJson').optional().isJSON(),
155
+ body('variables').optional().isArray()
156
+ ],
157
+ async (req, res) => {
158
+ const errors = validationResult(req);
159
+ if (!errors.isEmpty()) {
160
+ return res.status(400).json({ errors: errors.array() });
161
+ }
162
+
163
+ const { graphId } = req.params;
164
+ const { name, isEnabled, graphJson, variables, pluginOwnerId } = req.body;
165
+
166
+
167
+
168
+ try {
169
+ const dataToUpdate = {};
170
+ if (name !== undefined) dataToUpdate.name = name;
171
+ if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
172
+ if (pluginOwnerId !== undefined) dataToUpdate.pluginOwnerId = pluginOwnerId;
173
+
174
+ if (graphJson !== undefined) {
175
+ dataToUpdate.graphJson = graphJson;
176
+
177
+ const parsedGraph = JSON.parse(graphJson);
178
+ const eventNodes = parsedGraph.nodes.filter(node => node.type.startsWith('event:'));
179
+ const eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
180
+
181
+ const existingGraph = await prisma.eventGraph.findUnique({
182
+ where: { id: parseInt(graphId) },
183
+ include: { triggers: true }
184
+ });
185
+
186
+ const existingEventTypes = existingGraph?.triggers?.map(t => t.eventType) || [];
187
+
188
+ const eventTypesToDelete = existingEventTypes.filter(et => !eventTypes.includes(et));
189
+ const eventTypesToCreate = eventTypes.filter(et => !existingEventTypes.includes(et));
190
+
191
+ dataToUpdate.triggers = {
192
+ deleteMany: {},
193
+ create: eventTypes.map(eventType => ({ eventType })),
194
+ };
195
+ }
196
+
197
+ if (variables !== undefined) {
198
+ dataToUpdate.variables = Array.isArray(variables) ? JSON.stringify(variables) : variables;
199
+ }
200
+
201
+ const updatedGraph = await prisma.eventGraph.update({
202
+ where: { id: parseInt(graphId) },
203
+ data: dataToUpdate,
204
+ include: { triggers: true },
205
+ });
206
+
207
+ if (graphJson && updatedGraph.pluginOwnerId) {
208
+ try {
209
+ const plugin = await prisma.installedPlugin.findUnique({
210
+ where: { id: updatedGraph.pluginOwnerId }
211
+ });
212
+
213
+ if (plugin) {
214
+ const graphDir = path.join(plugin.path, 'graph');
215
+ await fse.mkdir(graphDir, { recursive: true });
216
+
217
+ const graphFile = path.join(graphDir, `${updatedGraph.name}.json`);
218
+ await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
219
+ console.log(`[API] Граф события ${updatedGraph.name} сохранен в ${graphFile}`);
220
+ }
221
+ } catch (error) {
222
+ console.error(`[API] Ошибка сохранения графа события в папку плагина:`, error);
223
+ }
224
+ }
225
+
226
+ res.json(updatedGraph);
227
+ } catch (error) {
228
+ logger.error(error, "--- ERROR CAUGHT IN EVENT GRAPH UPDATE ---");
229
+ res.status(500).json({ error: 'Failed to update event graph' });
230
+ }
231
+ }
232
+ );
233
+
234
+ router.delete('/:graphId',
235
+ authorize('management:edit'),
236
+ [param('graphId').isInt()],
237
+ async (req, res) => {
238
+ const errors = validationResult(req);
239
+ if (!errors.isEmpty()) {
240
+ return res.status(400).json({ errors: errors.array() });
241
+ }
242
+ const { graphId } = req.params;
243
+ try {
244
+ await prisma.eventGraph.delete({
245
+ where: { id: parseInt(graphId) },
246
+ });
247
+ res.status(204).send();
248
+ } catch (error) {
249
+ console.error('Failed to delete event graph:', error);
250
+ res.status(500).json({ error: 'Failed to delete event graph' });
251
+ }
252
+ }
253
+ );
254
+
255
+ router.get('/:graphId/export',
256
+ authorize('management:view'),
257
+ [param('graphId').isInt()],
258
+ async (req, res) => {
259
+ const errors = validationResult(req);
260
+ if (!errors.isEmpty()) {
261
+ return res.status(400).json({ errors: errors.array() });
262
+ }
263
+
264
+ const { graphId } = req.params;
265
+ try {
266
+ const eventGraph = await prisma.eventGraph.findUnique({
267
+ where: { id: parseInt(graphId) },
268
+ include: { triggers: true },
269
+ });
270
+
271
+ if (!eventGraph) {
272
+ return res.status(404).json({ error: 'Event graph not found' });
273
+ }
274
+
275
+ const exportData = {
276
+ name: eventGraph.name,
277
+ graphJson: eventGraph.graphJson,
278
+ variables: eventGraph.variables,
279
+ triggers: eventGraph.triggers.map(t => t.eventType),
280
+ };
281
+
282
+ res.json(exportData);
283
+ } catch (error) {
284
+ console.error('Failed to export event graph:', error);
285
+ res.status(500).json({ error: 'Failed to export event graph' });
286
+ }
287
+ }
288
+ );
289
+
290
+ router.post('/import',
291
+ authorize('management:edit'),
292
+ [
293
+ body('name').isString().notEmpty(),
294
+ body('graphJson').isJSON(),
295
+ body('variables').optional().isJSON(),
296
+ ],
297
+ async (req, res) => {
298
+ const errors = validationResult(req);
299
+ if (!errors.isEmpty()) {
300
+ return res.status(400).json({ errors: errors.array() });
301
+ }
302
+
303
+ const { botId } = req.params;
304
+ const { name, graphJson, variables } = req.body;
305
+
306
+ try {
307
+ const graph = JSON.parse(graphJson);
308
+ const idMap = new Map();
309
+
310
+ if (graph.nodes && Array.isArray(graph.nodes)) {
311
+ graph.nodes.forEach(node => {
312
+ if (node && node.id) {
313
+ const oldId = node.id;
314
+ const newId = `node_${crypto.randomUUID()}`;
315
+ idMap.set(oldId, newId);
316
+ node.id = newId;
317
+ }
318
+ });
319
+ }
320
+
321
+ const connectionList = graph.edges || graph.connections;
322
+ const updatedConnections = [];
323
+ if (connectionList && Array.isArray(connectionList)) {
324
+ connectionList.forEach(conn => {
325
+ if (conn && (conn.source || conn.sourceNodeId) && (conn.target || conn.targetNodeId)) {
326
+ const oldSourceId = conn.source || conn.sourceNodeId;
327
+ const oldTargetId = conn.target || conn.targetNodeId;
328
+
329
+ const newSourceId = idMap.get(oldSourceId);
330
+ const newTargetId = idMap.get(oldTargetId);
331
+
332
+ if (newSourceId && newTargetId) {
333
+ conn.id = `edge_${crypto.randomUUID()}`;
334
+ conn.sourceNodeId = newSourceId;
335
+ conn.targetNodeId = newTargetId;
336
+
337
+ delete conn.source;
338
+ delete conn.target;
339
+
340
+ updatedConnections.push(conn);
341
+ }
342
+ }
343
+ });
344
+
345
+ graph.connections = updatedConnections;
346
+ delete graph.edges;
347
+ }
348
+
349
+ const newGraphJson = JSON.stringify(graph);
350
+
351
+ const eventNodes = graph.nodes.filter(node => node.type.startsWith('event:'));
352
+ const eventTypes = eventNodes.map(node => node.type.split(':')[1]);
353
+
354
+ const newGraph = await prisma.eventGraph.create({
355
+ data: {
356
+ botId: parseInt(botId),
357
+ name: `${name} (копия)`,
358
+ graphJson: newGraphJson,
359
+ variables: variables || '[]',
360
+ isEnabled: false,
361
+ triggers: {
362
+ create: eventTypes.map(eventType => ({ eventType })),
363
+ },
364
+ },
365
+ include: { triggers: true }
366
+ });
367
+ res.status(201).json(newGraph);
368
+ } catch (error) {
369
+ if (error.code === 'P2002') {
370
+ return res.status(409).json({ error: `Event graph with name "${name} (копия)" already exists.` });
371
+ }
372
+ console.error("Failed to import event graph:", error);
373
+ res.status(500).json({ error: 'Failed to import event graph' });
374
+ }
375
+ }
376
+ );
377
+
378
+ router.post('/:graphId/duplicate',
379
+ authorize('management:edit'),
380
+ [param('graphId').isInt()],
381
+ async (req, res) => {
382
+ const errors = validationResult(req);
383
+ if (!errors.isEmpty()) {
384
+ return res.status(400).json({ errors: errors.array() });
385
+ }
386
+
387
+ const { botId, graphId } = req.params;
388
+
389
+ try {
390
+ const originalGraph = await prisma.eventGraph.findUnique({
391
+ where: { id: parseInt(graphId) },
392
+ include: { triggers: true },
393
+ });
394
+
395
+ if (!originalGraph) {
396
+ return res.status(404).json({ error: 'Original event graph not found' });
397
+ }
398
+
399
+ const graph = JSON.parse(originalGraph.graphJson);
400
+ const idMap = new Map();
401
+
402
+ if (graph.nodes && Array.isArray(graph.nodes)) {
403
+ graph.nodes.forEach(node => {
404
+ if (node && node.id) {
405
+ const oldId = node.id;
406
+ const newId = `node_${crypto.randomUUID()}`;
407
+ idMap.set(oldId, newId);
408
+ node.id = newId;
409
+ }
410
+ });
411
+ }
412
+
413
+ const connectionList = graph.connections || [];
414
+ const updatedConnections = [];
415
+ if (Array.isArray(connectionList)) {
416
+ connectionList.forEach(conn => {
417
+ if (conn && conn.sourceNodeId && conn.targetNodeId) {
418
+ const newSourceId = idMap.get(conn.sourceNodeId);
419
+ const newTargetId = idMap.get(conn.targetNodeId);
420
+
421
+ if (newSourceId && newTargetId) {
422
+ updatedConnections.push({
423
+ ...conn,
424
+ id: `edge_${crypto.randomUUID()}`,
425
+ sourceNodeId: newSourceId,
426
+ targetNodeId: newTargetId,
427
+ });
428
+ }
429
+ }
430
+ });
431
+ }
432
+ graph.connections = updatedConnections;
433
+
434
+ const newGraphJson = JSON.stringify(graph);
435
+ const newName = `${originalGraph.name} (копия)`;
436
+
437
+ const newGraph = await prisma.eventGraph.create({
438
+ data: {
439
+ botId: parseInt(botId),
440
+ name: newName,
441
+ graphJson: newGraphJson,
442
+ variables: originalGraph.variables,
443
+ isEnabled: false,
444
+ triggers: {
445
+ create: originalGraph.triggers.map(t => ({ eventType: t.eventType })),
446
+ },
447
+ },
448
+ });
449
+
450
+ res.status(201).json(newGraph);
451
+ } catch (error) {
452
+ logger.error('Failed to duplicate event graph:', error);
453
+ res.status(500).json({ error: 'Failed to duplicate event graph' });
454
+ }
455
+ }
456
+ );
457
+
458
+ module.exports = router;
459
+