✨ Features: - Modern SaaS automation platform - Next.js 15 + TypeScript frontend - Node.js + Express backend - PostgreSQL database with full schema - Docker Compose setup - Admin panel with analytics - Template marketplace (6 templates) - Integrations hub (10+ services) - Authentication & role-based access - Responsive n8n-style design 🎯 Ready for demo and deployment 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
456 lines
No EOL
12 KiB
TypeScript
456 lines
No EOL
12 KiB
TypeScript
import { Response } from 'express';
|
|
import { query } from '../utils/db';
|
|
import { AuthRequest } from '../middleware/auth';
|
|
import n8nService from '../services/n8nService';
|
|
|
|
export const getWorkflows = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
// Get workflows from n8n
|
|
const n8nWorkflows = await n8nService.getWorkflows();
|
|
|
|
// Get local workflow metadata
|
|
const localResult = await query(
|
|
`SELECT id, name, n8n_workflow_id, description, status, trigger_type, is_active, created_at, updated_at
|
|
FROM workflows WHERE user_id = $1 ORDER BY created_at DESC`,
|
|
[req.user?.id]
|
|
);
|
|
|
|
// Merge n8n data with local metadata
|
|
const workflows = localResult.rows.map(localWorkflow => {
|
|
const n8nWorkflow = n8nWorkflows.find(w => w.id === localWorkflow.n8n_workflow_id);
|
|
return {
|
|
...localWorkflow,
|
|
n8n_data: n8nWorkflow,
|
|
active: n8nWorkflow?.active || false,
|
|
status: n8nWorkflow?.active ? 'active' : 'inactive'
|
|
};
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
workflows,
|
|
total_n8n_workflows: n8nWorkflows.length
|
|
});
|
|
} catch (error) {
|
|
console.error('Get workflows error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to fetch workflows'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// Get local workflow
|
|
const localResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (localResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const localWorkflow = localResult.rows[0];
|
|
|
|
// Get n8n workflow if exists
|
|
let n8nWorkflow = null;
|
|
if (localWorkflow.n8n_workflow_id) {
|
|
try {
|
|
n8nWorkflow = await n8nService.getWorkflow(localWorkflow.n8n_workflow_id);
|
|
} catch (error) {
|
|
console.error('Failed to fetch n8n workflow:', error);
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
workflow: {
|
|
...localWorkflow,
|
|
n8n_data: n8nWorkflow
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const createWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { name, description, trigger_type, trigger_config, actions_config, n8n_workflow_data } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Name is required'
|
|
});
|
|
}
|
|
|
|
let n8nWorkflowId = null;
|
|
|
|
// Create workflow in n8n if workflow data provided
|
|
if (n8n_workflow_data) {
|
|
try {
|
|
const n8nWorkflow = await n8nService.createWorkflow({
|
|
name,
|
|
active: false,
|
|
nodes: n8n_workflow_data.nodes || [],
|
|
connections: n8n_workflow_data.connections || {},
|
|
settings: n8n_workflow_data.settings,
|
|
staticData: n8n_workflow_data.staticData
|
|
});
|
|
n8nWorkflowId = n8nWorkflow.id;
|
|
} catch (error) {
|
|
console.error('Failed to create n8n workflow:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to create workflow in n8n'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Save to local database
|
|
const result = await query(
|
|
`INSERT INTO workflows (user_id, name, description, trigger_type, trigger_config, actions_config, n8n_workflow_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, name, description, status, trigger_type, is_active, created_at`,
|
|
[req.user?.id, name, description, trigger_type, trigger_config, actions_config, n8nWorkflowId]
|
|
);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
workflow: {
|
|
...result.rows[0],
|
|
n8n_workflow_id: n8nWorkflowId
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Create workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const updateWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, trigger_type, trigger_config, actions_config, n8n_workflow_data } = req.body;
|
|
|
|
// Get existing workflow
|
|
const existingResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (existingResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const existingWorkflow = existingResult.rows[0];
|
|
|
|
// Update n8n workflow if exists
|
|
if (existingWorkflow.n8n_workflow_id && n8n_workflow_data) {
|
|
try {
|
|
await n8nService.updateWorkflow(existingWorkflow.n8n_workflow_id, {
|
|
name,
|
|
nodes: n8n_workflow_data.nodes,
|
|
connections: n8n_workflow_data.connections,
|
|
settings: n8n_workflow_data.settings,
|
|
staticData: n8n_workflow_data.staticData
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to update n8n workflow:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to update workflow in n8n'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update local database
|
|
const result = await query(
|
|
`UPDATE workflows
|
|
SET name = $1, description = $2, trigger_type = $3, trigger_config = $4, actions_config = $5, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $6 AND user_id = $7
|
|
RETURNING id, name, description, status, trigger_type, is_active, created_at, updated_at`,
|
|
[name, description, trigger_type, trigger_config, actions_config, id, req.user?.id]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
workflow: result.rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Update workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const activateWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// Get workflow
|
|
const workflowResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (workflowResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const workflow = workflowResult.rows[0];
|
|
|
|
// Activate in n8n
|
|
if (workflow.n8n_workflow_id) {
|
|
try {
|
|
await n8nService.activateWorkflow(workflow.n8n_workflow_id);
|
|
} catch (error) {
|
|
console.error('Failed to activate n8n workflow:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to activate workflow in n8n'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update local status
|
|
await query(
|
|
`UPDATE workflows SET is_active = true, status = 'active', updated_at = CURRENT_TIMESTAMP WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Workflow activated successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Activate workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const deactivateWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// Get workflow
|
|
const workflowResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (workflowResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const workflow = workflowResult.rows[0];
|
|
|
|
// Deactivate in n8n
|
|
if (workflow.n8n_workflow_id) {
|
|
try {
|
|
await n8nService.deactivateWorkflow(workflow.n8n_workflow_id);
|
|
} catch (error) {
|
|
console.error('Failed to deactivate n8n workflow:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to deactivate workflow in n8n'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update local status
|
|
await query(
|
|
`UPDATE workflows SET is_active = false, status = 'inactive', updated_at = CURRENT_TIMESTAMP WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Workflow deactivated successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Deactivate workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const executeWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { inputData } = req.body;
|
|
|
|
// Get workflow
|
|
const workflowResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (workflowResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const workflow = workflowResult.rows[0];
|
|
|
|
if (!workflow.n8n_workflow_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Workflow is not connected to n8n'
|
|
});
|
|
}
|
|
|
|
// Execute in n8n
|
|
try {
|
|
const execution = await n8nService.executeWorkflow(workflow.n8n_workflow_id, inputData);
|
|
|
|
// Log execution in database
|
|
await query(
|
|
`INSERT INTO workflow_executions (workflow_id, user_id, n8n_execution_id, status, trigger_data, started_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
[id, req.user?.id, execution.id, execution.finished ? 'completed' : 'running', inputData, new Date()]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
execution
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to execute workflow:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to execute workflow'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Execute workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const deleteWorkflow = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// Get workflow
|
|
const workflowResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (workflowResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const workflow = workflowResult.rows[0];
|
|
|
|
// Delete from n8n first
|
|
if (workflow.n8n_workflow_id) {
|
|
try {
|
|
await n8nService.deleteWorkflow(workflow.n8n_workflow_id);
|
|
} catch (error) {
|
|
console.error('Failed to delete n8n workflow:', error);
|
|
// Continue with local deletion even if n8n deletion fails
|
|
}
|
|
}
|
|
|
|
// Delete from local database
|
|
await query(
|
|
'DELETE FROM workflows WHERE id = $1 AND user_id = $2',
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Workflow deleted successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Delete workflow error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getWorkflowExecutions = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const limit = parseInt(req.query.limit as string) || 20;
|
|
|
|
// Get workflow
|
|
const workflowResult = await query(
|
|
`SELECT * FROM workflows WHERE id = $1 AND user_id = $2`,
|
|
[id, req.user?.id]
|
|
);
|
|
|
|
if (workflowResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Workflow not found'
|
|
});
|
|
}
|
|
|
|
const workflow = workflowResult.rows[0];
|
|
|
|
if (!workflow.n8n_workflow_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Workflow is not connected to n8n'
|
|
});
|
|
}
|
|
|
|
// Get executions from n8n
|
|
const executions = await n8nService.getExecutions(workflow.n8n_workflow_id, limit);
|
|
|
|
res.json({
|
|
success: true,
|
|
executions
|
|
});
|
|
} catch (error) {
|
|
console.error('Get workflow executions error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
}; |