semillero-special-hotel/scripts/n8n-bootstrap.js
gabogg 41fb0d0d75 feat: implement phase 5 settlements and i18n support
- Add English/Spanish translation system for UI pages
- Add bilingual AI audit notes using translation LLM chains in n8n
- Support zero-trust Docker routing to dev stack via internal hostnames
- Quiet down Next.js database query and middleware logging in test runs
2026-06-12 19:00:46 +00:00

222 lines
7.8 KiB
JavaScript

const fs = require('fs');
const path = require('path');
require('dotenv').config();
async function bootstrap() {
console.log("=== STARTING N8N WORKFLOW BOOTSTRAP / KICKSTART ===");
// 1. Fetch n8n API configuration from .agents/mcp_config.json
const mcpConfigPath = path.join(__dirname, '../.agents/mcp_config.json');
if (!fs.existsSync(mcpConfigPath)) {
console.error("Error: .agents/mcp_config.json not found.");
process.exit(1);
}
const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
const n8nEnv = mcpConfig.mcpServers.n8n.env;
const n8nUrl = n8nEnv.N8N_API_URL || "https://n8n.gaboggamer.online";
const n8nApiKey = n8nEnv.N8N_API_KEY;
if (!n8nApiKey) {
console.error("Error: N8N_API_KEY is missing in mcp_config.json.");
process.exit(1);
}
// 2. Fetch credentials dynamically to resolve AI accounts
console.log("Fetching credentials from n8n to resolve AI accounts...");
let deepSeekCred = null;
let geminiCred = null;
try {
const credsRes = await fetch(`${n8nUrl}/api/v1/credentials`, {
method: 'GET',
headers: {
'X-N8N-API-KEY': n8nApiKey
}
});
if (credsRes.ok) {
const credsData = await credsRes.json();
const credentials = Array.isArray(credsData.data) ? credsData.data : (credsData.data.credentials || []);
const deepSeekCreds = credentials.filter(c => c.type === 'deepSeekApi');
if (deepSeekCreds.length > 0) {
deepSeekCred = deepSeekCreds.find(c => c.name.toLowerCase().includes('deepseek')) || deepSeekCreds[0];
}
const geminiCreds = credentials.filter(c => c.type === 'googlePalmApi');
if (geminiCreds.length > 0) {
geminiCred = geminiCreds.find(c => c.name.toLowerCase().includes('gemini') || c.name.toLowerCase().includes('google')) || geminiCreds[0];
}
if (deepSeekCred) {
console.log(`Resolved DeepSeek credential: "${deepSeekCred.name}" (ID: ${deepSeekCred.id})`);
}
if (geminiCred) {
console.log(`Resolved Gemini credential: "${geminiCred.name}" (ID: ${geminiCred.id})`);
}
} else {
console.warn(`Warning: Failed to fetch credentials. Status: ${credsRes.status}`);
}
} catch (err) {
console.warn("Warning: Could not fetch credentials dynamically:", err.message);
}
// 3. Fetch project ID dynamically
console.log("Fetching workflows list to resolve project ID...");
const listRes = await fetch(`${n8nUrl}/api/v1/workflows`, {
method: 'GET',
headers: {
'X-N8N-API-KEY': n8nApiKey
}
});
if (!listRes.ok) {
console.error(`Failed to list workflows from n8n. Status: ${listRes.status}`);
process.exit(1);
}
const listData = await listRes.json();
let projectId;
const firstWf = listData.data.find(w => w.shared && w.shared.length > 0);
if (firstWf) {
projectId = firstWf.shared[0].projectId;
console.log(`Resolved project ID dynamically: ${projectId}`);
}
const workflowsToBootstrap = [
'sales_import_workflow.json',
'settlement_calculation_workflow.json'
];
for (const filename of workflowsToBootstrap) {
console.log(`\n--- Bootstrapping workflow: ${filename} ---`);
const workflowPath = path.join(__dirname, '../n8n', filename);
if (!fs.existsSync(workflowPath)) {
console.error(`Error: ${workflowPath} not found.`);
continue;
}
const workflowJson = JSON.parse(fs.readFileSync(workflowPath, 'utf8'));
const targetName = workflowJson.name;
// Inject resolved credentials dynamically into the workflow JSON
for (const node of workflowJson.nodes) {
if (node.type === '@n8n/n8n-nodes-langchain.chainLlm') {
// Find deepseek node and gemini node to map credentials
}
if (node.type === '@n8n/n8n-nodes-langchain.lmChatDeepSeek' && deepSeekCred) {
node.credentials = {
deepSeekApi: {
id: deepSeekCred.id,
name: deepSeekCred.name
}
};
console.log(`Injected DeepSeek credential into node "${node.name}" in ${filename}`);
}
if (node.type === '@n8n/n8n-nodes-langchain.lmChatGoogleGemini' && geminiCred) {
node.credentials = {
googlePalmApi: {
id: geminiCred.id,
name: geminiCred.name
}
};
console.log(`Injected Gemini credential into node "${node.name}" in ${filename}`);
}
if (node.id === 'Set-Dev-Env') {
const appUrl = process.env.APP_DEV_INTERNAL_URL || "http://special-hotel-dev:3000";
const signature = process.env.N8N_WEBHOOK_SECRET_DEV || process.env.N8N_WEBHOOK_SECRET || "qUHuPqPjA65psdtwQU7zgp/DVkvd1xXk2WP/vzbnEdc=";
node.parameters.values.string = [
{ name: "appUrl", value: appUrl },
{ name: "signature", value: signature }
];
console.log(`Injected Dev environment configuration into node "${node.name}" in ${filename}`);
}
if (node.id === 'Set-Prod-Env') {
const appUrl = process.env.APP_PROD_INTERNAL_URL || "http://special-hotel-prod:3000";
const signature = process.env.N8N_WEBHOOK_SECRET || "Ecjb2s33tHJppNBDJ/DxXEjHWKow8bNWmsQrk1sQKyQ=";
node.parameters.values.string = [
{ name: "appUrl", value: appUrl },
{ name: "signature", value: signature }
];
console.log(`Injected Prod environment configuration into node "${node.name}" in ${filename}`);
}
}
const existingWorkflow = listData.data.find(w => w.name === targetName);
let workflowId;
if (existingWorkflow) {
workflowId = existingWorkflow.id;
console.log(`Found existing workflow "${targetName}". ID: ${workflowId}. Updating...`);
const updateRes = await fetch(`${n8nUrl}/api/v1/workflows/${workflowId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-N8N-API-KEY': n8nApiKey
},
body: JSON.stringify({
name: targetName,
nodes: workflowJson.nodes,
connections: workflowJson.connections,
settings: workflowJson.settings || {}
})
});
if (!updateRes.ok) {
console.error(`Failed to update workflow. Status: ${updateRes.status}`);
process.exit(1);
}
console.log(`Workflow updated successfully!`);
} else {
console.log(`No existing workflow found for "${targetName}". Creating new workflow...`);
const createRes = await fetch(`${n8nUrl}/api/v1/workflows`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-N8N-API-KEY': n8nApiKey
},
body: JSON.stringify({
name: targetName,
nodes: workflowJson.nodes,
connections: workflowJson.connections,
settings: workflowJson.settings || {},
projectId
})
});
if (!createRes.ok) {
console.error(`Failed to create workflow. Status: ${createRes.status}`);
process.exit(1);
}
const createData = await createRes.json();
workflowId = createData.id;
console.log(`Workflow created successfully! ID: ${workflowId}`);
}
// Activate the workflow
console.log(`Activating workflow "${targetName}" (${workflowId})...`);
const activateRes = await fetch(`${n8nUrl}/api/v1/workflows/${workflowId}/activate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-N8N-API-KEY': n8nApiKey
},
body: JSON.stringify({})
});
if (!activateRes.ok) {
console.error(`Warning: Failed to activate workflow. Status: ${activateRes.status}`);
} else {
console.log(`Workflow "${targetName}" activated and ready!`);
}
}
console.log("\n=== N8N WORKFLOW BOOTSTRAP COMPLETED SUCCESSFULLY ===");
}
bootstrap().catch(err => {
console.error("Fatal bootstrap error:", err);
process.exit(1);
});