Refactor backend and frontend: Added migration scripts, updated dependencies, improved error handling, and enhanced workflow editor functionality. Removed version from docker-compose file.

This commit is contained in:
Mehmet Oezdag 2025-06-08 15:55:14 +02:00
parent 74fb343ead
commit 1c317d1a1d
13 changed files with 21420 additions and 66 deletions

30
backend/knexfile.js Normal file
View File

@ -0,0 +1,30 @@
require('dotenv').config();
module.exports = {
development: {
client: 'pg',
connection: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/flowforge',
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'knex_migrations',
directory: './migrations'
}
},
production: {
client: 'pg',
connection: process.env.DATABASE_URL,
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'knex_migrations',
directory: './migrations'
},
ssl: process.env.DATABASE_SSL === 'true' ? { rejectUnauthorized: false } : false
}
};

View File

@ -70,3 +70,133 @@
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-07 08:55:59"} {"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-07 08:55:59"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-07 08:56:07"} {"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-07 08:56:07"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-07 08:56:07"} {"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-07 08:56:07"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:30:14"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:30:14"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:31:23"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:31:23"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:31:25"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:31:25"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:31:35"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:31:35"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:32:07"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:32:07"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Node registry initialized with 3 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Node registry initialized with 3 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"error":"Cannot find module 'isolated-vm'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Node registry initialized with 5 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}

View File

@ -32,3 +32,10 @@
{"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/971f09e0-6490-4be3-a6cb-928e9f2ca5a5","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:08"} {"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/971f09e0-6490-4be3-a6cb-928e9f2ca5a5","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:08"}
{"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/7140abc0-968a-4728-925b-27f49f139b6d","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:12"} {"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/7140abc0-968a-4728-925b-27f49f139b6d","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:12"}
{"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/7140abc0-968a-4728-925b-27f49f139b6d","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:12"} {"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/7140abc0-968a-4728-925b-27f49f139b6d","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:12"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'isolated-vm'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}

5751
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,13 @@
"scripts": { "scripts": {
"start": "node src/index.js", "start": "node src/index.js",
"dev": "nodemon src/index.js", "dev": "nodemon src/index.js",
"test": "jest" "test": "jest",
"migrate": "knex migrate:latest",
"migrate:rollback": "knex migrate:rollback",
"migrate:status": "knex migrate:status"
}, },
"dependencies": { "dependencies": {
"axios": "^1.4.0",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"bull": "^4.10.4", "bull": "^4.10.4",
"cors": "^2.8.5", "cors": "^2.8.5",
@ -16,8 +20,10 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-rate-limit": "^6.7.0", "express-rate-limit": "^6.7.0",
"helmet": "^6.1.5", "helmet": "^6.1.5",
"isolated-vm": "^5.0.1",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"knex": "^2.4.2", "knex": "^2.4.2",
"nodemailer": "^6.9.3",
"pg": "^8.10.0", "pg": "^8.10.0",
"redis": "^4.6.6", "redis": "^4.6.6",
"uuid": "^9.0.0", "uuid": "^9.0.0",

View File

@ -5,6 +5,7 @@ const helmet = require('helmet');
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const { errorHandler } = require('./middleware/errorHandler'); const { errorHandler } = require('./middleware/errorHandler');
const logger = require('./utils/logger'); const logger = require('./utils/logger');
const nodeRegistry = require('./services/nodeRegistry');
// Import routes // Import routes
const authRoutes = require('./routes/auth'); const authRoutes = require('./routes/auth');
@ -24,7 +25,7 @@ app.use(express.json());
// Rate limiting // Rate limiting
const limiter = rateLimit({ const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs max: 1000, // limit each IP to 1000 requests per windowMs (increased for dev)
standardHeaders: true, standardHeaders: true,
legacyHeaders: false, legacyHeaders: false,
}); });
@ -44,9 +45,18 @@ app.get('/health', (req, res) => {
// Error handling middleware // Error handling middleware
app.use(errorHandler); app.use(errorHandler);
// Initialize services
const initializeServices = async () => {
// Initialize node registry
await nodeRegistry.initialize();
};
// Start server // Start server
app.listen(PORT, () => { app.listen(PORT, async () => {
logger.info(`Server running on port ${PORT}`); logger.info(`Server running on port ${PORT}`);
// Initialize services after server starts
await initializeServices();
}); });
// Handle unhandled promise rejections // Handle unhandled promise rejections

View File

@ -1,4 +1,4 @@
const { VM } = require('vm2'); const ivm = require('isolated-vm');
const logger = require('../../utils/logger'); const logger = require('../../utils/logger');
/** /**
@ -17,53 +17,57 @@ async function run(nodeConfig, inputData) {
// Set timeout (default: 5000ms) // Set timeout (default: 5000ms)
const timeout = nodeConfig.timeout || 5000; const timeout = nodeConfig.timeout || 5000;
// Create a sandboxed VM // Create a new isolated context
const vm = new VM({ const isolate = new ivm.Isolate({ memoryLimit: 32 });
timeout, const context = await isolate.createContext();
sandbox: {
// Provide input data to the sandbox // Get a reference to the global object
input: inputData.input || {}, const jail = context.global;
// Provide console methods that log to our logger
console: { // Add input data to the context
await jail.set('input', new ivm.ExternalCopy(inputData.input || {}).copyInto());
// Add console methods that log to our logger
const logMethods = {
log: (...args) => logger.info('Function node log:', ...args), log: (...args) => logger.info('Function node log:', ...args),
info: (...args) => logger.info('Function node info:', ...args), info: (...args) => logger.info('Function node info:', ...args),
warn: (...args) => logger.warn('Function node warn:', ...args), warn: (...args) => logger.warn('Function node warn:', ...args),
error: (...args) => logger.error('Function node error:', ...args) error: (...args) => logger.error('Function node error:', ...args)
} };
},
// Prevent access to Node.js internal modules
require: {
external: false,
builtin: ['path', 'util', 'buffer'],
root: "./",
mock: {
fs: {
readFileSync: () => 'Not allowed'
}
}
}
});
// Wrap the code in an async function await jail.set('console', new ivm.ExternalCopy(logMethods).copyInto());
// Wrap the code in an async function that returns the result
const wrappedCode = ` const wrappedCode = `
(async function() { (async function() {
try {
${nodeConfig.code} ${nodeConfig.code}
} catch (error) {
throw new Error(error.message);
}
})(); })();
`; `;
// Execute the code // Execute the code with timeout
const result = await vm.run(wrappedCode); const script = await isolate.compileScript(wrappedCode);
const result = await script.run(context, { timeout });
// Validate the result // Handle the result
if (result === undefined || result === null) { let output = {};
return { if (result !== undefined && result !== null) {
output: {} // If result is a primitive value, wrap it
}; if (typeof result === 'object') {
output = result;
} else {
output = { value: result };
}
} }
// Return the result // Clean up
isolate.dispose();
return { return {
output: result output: output
}; };
} catch (error) { } catch (error) {
logger.error('Function node error', { error: error.message }); logger.error('Function node error', { error: error.message });

View File

@ -1,5 +1,3 @@
version: '3.8'
services: services:
frontend: frontend:
build: build:

15260
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"reactflow": "^11.7.0", "reactflow": "^11.10.4",
"react-router-dom": "^6.14.1", "react-router-dom": "^6.14.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",

View File

@ -31,7 +31,7 @@ const getNodeIcon = (type) => {
case 'http-request': case 'http-request':
return ( return (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg> </svg>
); );
case 'function': case 'function':
@ -67,21 +67,66 @@ const getNodeIcon = (type) => {
} }
}; };
const CustomNode = ({ data, selected }) => { const CustomNode = ({ id, data, selected, onEdit, onDelete }) => {
const nodeColor = getNodeColor(data.nodeType); const nodeColor = getNodeColor(data.nodeType);
const nodeIcon = getNodeIcon(data.nodeType); const nodeIcon = getNodeIcon(data.nodeType);
const handleEdit = (e) => {
e.stopPropagation();
if (onEdit) {
onEdit(id);
}
};
const handleDelete = (e) => {
e.stopPropagation();
if (onDelete) {
onDelete(id);
}
};
return ( return (
<div className={`${selected ? 'ring-2 ring-primary-400' : ''} bg-white rounded-md shadow-md border-l-4 ${nodeColor} p-3 min-w-[180px]`}> <div
className={`group relative ${selected ? 'ring-2 ring-primary-400' : ''} bg-white rounded-md shadow-md border-l-4 ${nodeColor} p-3 min-w-[180px] cursor-grab active:cursor-grabbing hover:shadow-lg transition-shadow`}
style={{ pointerEvents: 'auto' }}
>
{/* Action buttons - show on hover or when selected */}
<div className={`absolute -top-2 -right-2 flex space-x-1 transition-opacity ${selected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`}>
{/* Edit button */}
<button
onClick={handleEdit}
className="w-6 h-6 bg-blue-500 hover:bg-blue-600 text-white rounded-full flex items-center justify-center shadow-md transition-colors"
style={{ pointerEvents: 'auto' }}
title="Edit node"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
{/* Delete button */}
<button
onClick={handleDelete}
className="w-6 h-6 bg-red-500 hover:bg-red-600 text-white rounded-full flex items-center justify-center shadow-md transition-colors"
style={{ pointerEvents: 'auto' }}
title="Delete node"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
{/* Input handle */} {/* Input handle */}
<Handle <Handle
type="target" type="target"
position={Position.Top} position={Position.Top}
className="w-3 h-3 rounded-full bg-gray-400 border-2 border-white" className="w-3 h-3 rounded-full bg-gray-400 border-2 border-white"
style={{ pointerEvents: 'auto' }}
/> />
{/* Node content */} {/* Node content */}
<div className="flex items-center"> <div className="flex items-center" style={{ pointerEvents: 'none' }}>
<div className="flex-shrink-0 text-gray-500"> <div className="flex-shrink-0 text-gray-500">
{nodeIcon} {nodeIcon}
</div> </div>
@ -100,6 +145,7 @@ const CustomNode = ({ data, selected }) => {
type="source" type="source"
position={Position.Bottom} position={Position.Bottom}
className="w-3 h-3 rounded-full bg-gray-400 border-2 border-white" className="w-3 h-3 rounded-full bg-gray-400 border-2 border-white"
style={{ pointerEvents: 'auto' }}
/> />
</div> </div>
); );

View File

@ -20,6 +20,13 @@ code {
/* Custom styles for react-flow */ /* Custom styles for react-flow */
.react-flow__node { .react-flow__node {
@apply shadow-md rounded-md; @apply shadow-md rounded-md;
cursor: grab;
user-select: none;
pointer-events: auto;
}
.react-flow__node.dragging {
cursor: grabbing;
} }
.react-flow__node-default { .react-flow__node-default {
@ -28,6 +35,7 @@ code {
.react-flow__handle { .react-flow__handle {
@apply w-3 h-3; @apply w-3 h-3;
pointer-events: auto;
} }
.react-flow__handle-top { .react-flow__handle-top {
@ -46,6 +54,11 @@ code {
@apply shadow-md; @apply shadow-md;
} }
/* Ensure React Flow pane is properly configured */
.react-flow__pane {
cursor: default;
}
/* Node types */ /* Node types */
.node-webhook { .node-webhook {
@apply border-l-4 border-l-blue-500; @apply border-l-4 border-l-blue-500;

View File

@ -48,10 +48,18 @@ const WorkflowEditor = () => {
const [latestExecutionId, setLatestExecutionId] = useState(null); const [latestExecutionId, setLatestExecutionId] = useState(null);
const [currentVersion, setCurrentVersion] = useState(1); const [currentVersion, setCurrentVersion] = useState(1);
const [showTabs, setShowTabs] = useState(true); const [showTabs, setShowTabs] = useState(true);
const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1 });
const [isFirstLoad, setIsFirstLoad] = useState(true);
// Node type definitions for React Flow // Node type definitions for React Flow
const nodeTypeDefinitions = { const nodeTypeDefinitions = {
customNode: CustomNode customNode: (nodeProps) => (
<CustomNode
{...nodeProps}
onEdit={handleEditNode}
onDelete={handleDeleteNode}
/>
)
}; };
// Load workflow if editing existing one // Load workflow if editing existing one
@ -82,12 +90,17 @@ const WorkflowEditor = () => {
const flowNodes = workflowData.nodes.map(node => ({ const flowNodes = workflowData.nodes.map(node => ({
id: node.id, id: node.id,
type: 'customNode', type: 'customNode',
position: { x: node.position_x, y: node.position_y }, position: {
x: Number(node.position_x) || 0,
y: Number(node.position_y) || 0
},
data: { data: {
label: node.name, label: node.name,
nodeType: node.type, nodeType: node.type,
config: node.config || {} config: node.config || {}
} },
draggable: true,
selectable: true
})); }));
const flowEdges = workflowData.connections.map(conn => ({ const flowEdges = workflowData.connections.map(conn => ({
@ -110,7 +123,7 @@ const WorkflowEditor = () => {
}; };
fetchData(); fetchData();
}, [id]); }, [id, setNodes, setEdges]);
// Handle connections between nodes // Handle connections between nodes
const onConnect = useCallback((params) => { const onConnect = useCallback((params) => {
@ -151,6 +164,8 @@ const WorkflowEditor = () => {
nodeType: nodeData.type, nodeType: nodeData.type,
config: {} config: {}
}, },
draggable: true,
selectable: true
}; };
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
@ -283,18 +298,35 @@ const WorkflowEditor = () => {
position: { position: {
x: node.position.x + 50, x: node.position.x + 50,
y: node.position.y + 50 y: node.position.y + 50
} },
draggable: true,
selectable: true
}; };
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
}; };
// Delete a node // Handle node editing
const handleDeleteNode = (node) => { const handleEditNode = useCallback((nodeId) => {
setNodes((nds) => nds.filter((n) => n.id !== node.id)); const node = nodes.find(n => n.id === nodeId);
setEdges((eds) => eds.filter((e) => e.source !== node.id && e.target !== node.id)); if (node) {
setSelectedNode(node);
}
}, [nodes]);
// Handle node deletion
const handleDeleteNode = useCallback((nodeId) => {
// Show confirmation dialog
if (window.confirm('Are you sure you want to delete this node?')) {
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
// Clear selection if the deleted node was selected
if (selectedNode && selectedNode.id === nodeId) {
setSelectedNode(null); setSelectedNode(null);
}; }
}
}, [setNodes, setEdges, selectedNode]);
// Handle workflow execution // Handle workflow execution
const handleExecuteWorkflow = (executionId) => { const handleExecuteWorkflow = (executionId) => {
@ -320,12 +352,17 @@ const WorkflowEditor = () => {
const flowNodes = workflowData.nodes.map(node => ({ const flowNodes = workflowData.nodes.map(node => ({
id: node.id, id: node.id,
type: 'customNode', type: 'customNode',
position: { x: node.position_x, y: node.position_y }, position: {
x: Number(node.position_x) || 0,
y: Number(node.position_y) || 0
},
data: { data: {
label: node.name, label: node.name,
nodeType: node.type, nodeType: node.type,
config: node.config || {} config: node.config || {}
} },
draggable: true,
selectable: true
})); }));
const flowEdges = workflowData.connections.map(conn => ({ const flowEdges = workflowData.connections.map(conn => ({
@ -339,6 +376,8 @@ const WorkflowEditor = () => {
setNodes(flowNodes); setNodes(flowNodes);
setEdges(flowEdges); setEdges(flowEdges);
setCurrentVersion(version); setCurrentVersion(version);
// Don't reset viewport when restoring versions - preserve user's current view
toast.success(`Restored workflow to version ${version}`); toast.success(`Restored workflow to version ${version}`);
} catch (error) { } catch (error) {
console.error('Error restoring version:', error); console.error('Error restoring version:', error);
@ -348,6 +387,55 @@ const WorkflowEditor = () => {
} }
}; };
// Handle node changes
const handleNodesChange = useCallback((changes) => {
onNodesChange(changes);
}, [onNodesChange]);
// Handle edge changes
const handleEdgesChange = useCallback((changes) => {
onEdgesChange(changes);
}, [onEdgesChange]);
// Handle viewport changes to maintain pan/zoom state
const onViewportChange = useCallback((newViewport) => {
setViewport(newViewport);
// Save viewport to localStorage for this workflow
if (id && id !== 'new') {
localStorage.setItem(`workflow-viewport-${id}`, JSON.stringify(newViewport));
}
}, [id]);
// Load saved viewport for existing workflows
useEffect(() => {
if (id && id !== 'new') {
const savedViewport = localStorage.getItem(`workflow-viewport-${id}`);
if (savedViewport) {
try {
const parsedViewport = JSON.parse(savedViewport);
setViewport(parsedViewport);
} catch (error) {
console.warn('Failed to parse saved viewport:', error);
}
}
}
}, [id]);
// Handle React Flow initialization
const onReactFlowInit = useCallback((reactFlowInstance) => {
setReactFlowInstance(reactFlowInstance);
// Only fit view on first load of new workflows, or if no saved viewport
if (isFirstLoad && (!id || id === 'new' || !localStorage.getItem(`workflow-viewport-${id}`))) {
setTimeout(() => {
reactFlowInstance.fitView({ padding: 0.1, maxZoom: 1.2 });
setIsFirstLoad(false);
}, 100);
} else {
setIsFirstLoad(false);
}
}, [isFirstLoad, id]);
return ( return (
<div className="h-[calc(100vh-6rem)]"> <div className="h-[calc(100vh-6rem)]">
{isLoading ? ( {isLoading ? (
@ -387,7 +475,7 @@ const WorkflowEditor = () => {
workflowId={id} workflowId={id}
selectedNode={selectedNode} selectedNode={selectedNode}
onDuplicateNode={handleDuplicateNode} onDuplicateNode={handleDuplicateNode}
onDeleteNode={handleDeleteNode} onDeleteNode={(node) => handleDeleteNode(node.id)}
onExecuteWorkflow={handleExecuteWorkflow} onExecuteWorkflow={handleExecuteWorkflow}
onTestNode={() => setShowNodeTester(true)} onTestNode={() => setShowNodeTester(true)}
/> />
@ -422,16 +510,27 @@ const WorkflowEditor = () => {
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={handleEdgesChange}
onConnect={onConnect} onConnect={onConnect}
onInit={setReactFlowInstance} onInit={onReactFlowInit}
onViewportChange={onViewportChange}
onDrop={onDrop} onDrop={onDrop}
onDragOver={onDragOver} onDragOver={onDragOver}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onPaneClick={onPaneClick} onPaneClick={onPaneClick}
nodeTypes={nodeTypeDefinitions} nodeTypes={nodeTypeDefinitions}
fitView nodesDraggable={true}
nodesConnectable={true}
elementsSelectable={true}
panOnDrag={true}
panOnScroll={true}
zoomOnScroll={true}
zoomOnPinch={true}
zoomOnDoubleClick={false}
selectNodesOnDrag={false}
viewport={viewport}
fitViewOnInit={false}
> >
<Controls /> <Controls />
<MiniMap /> <MiniMap />