n8n-mcp
Version:
Integration between n8n workflow automation and Model Context Protocol (MCP)
233 lines • 10.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemplateRepository = void 0;
const logger_1 = require("../utils/logger");
const template_sanitizer_1 = require("../utils/template-sanitizer");
class TemplateRepository {
constructor(db) {
this.db = db;
this.hasFTS5Support = false;
this.sanitizer = new template_sanitizer_1.TemplateSanitizer();
this.initializeFTS5();
}
initializeFTS5() {
this.hasFTS5Support = this.db.checkFTS5Support();
if (this.hasFTS5Support) {
try {
const ftsExists = this.db.prepare(`
SELECT name FROM sqlite_master
WHERE type='table' AND name='templates_fts'
`).get();
if (ftsExists) {
logger_1.logger.info('FTS5 table already exists for templates');
try {
const testCount = this.db.prepare('SELECT COUNT(*) as count FROM templates_fts').get();
logger_1.logger.info(`FTS5 enabled with ${testCount.count} indexed entries`);
}
catch (testError) {
logger_1.logger.warn('FTS5 table exists but query failed:', testError);
this.hasFTS5Support = false;
return;
}
}
else {
logger_1.logger.info('Creating FTS5 virtual table for templates...');
this.db.exec(`
CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5(
name, description, content=templates
);
`);
this.db.exec(`
CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN
INSERT INTO templates_fts(rowid, name, description)
VALUES (new.id, new.name, new.description);
END;
`);
this.db.exec(`
CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN
UPDATE templates_fts SET name = new.name, description = new.description
WHERE rowid = new.id;
END;
`);
this.db.exec(`
CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN
DELETE FROM templates_fts WHERE rowid = old.id;
END;
`);
logger_1.logger.info('FTS5 support enabled for template search');
}
}
catch (error) {
logger_1.logger.warn('Failed to initialize FTS5 for templates:', {
message: error.message,
code: error.code,
stack: error.stack
});
this.hasFTS5Support = false;
}
}
else {
logger_1.logger.info('FTS5 not available, using LIKE search for templates');
}
}
saveTemplate(workflow, detail, categories = []) {
const stmt = this.db.prepare(`
INSERT OR REPLACE INTO templates (
id, workflow_id, name, description, author_name, author_username,
author_verified, nodes_used, workflow_json, categories, views,
created_at, updated_at, url
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const nodeTypes = detail.workflow.nodes.map(n => n.type);
const url = `https://n8n.io/workflows/${workflow.id}`;
const { sanitized: sanitizedWorkflow, wasModified } = this.sanitizer.sanitizeWorkflow(detail.workflow);
if (wasModified) {
const detectedTokens = this.sanitizer.detectTokens(detail.workflow);
logger_1.logger.warn(`Sanitized API tokens in template ${workflow.id}: ${workflow.name}`, {
templateId: workflow.id,
templateName: workflow.name,
tokensFound: detectedTokens.length,
tokenPreviews: detectedTokens.map(t => t.substring(0, 20) + '...')
});
}
stmt.run(workflow.id, workflow.id, workflow.name, workflow.description || '', workflow.user.name, workflow.user.username, workflow.user.verified ? 1 : 0, JSON.stringify(nodeTypes), JSON.stringify(sanitizedWorkflow), JSON.stringify(categories), workflow.totalViews || 0, workflow.createdAt, workflow.createdAt, url);
}
getTemplatesByNodes(nodeTypes, limit = 10) {
const conditions = nodeTypes.map(() => "nodes_used LIKE ?").join(" OR ");
const query = `
SELECT * FROM templates
WHERE ${conditions}
ORDER BY views DESC, created_at DESC
LIMIT ?
`;
const params = [...nodeTypes.map(n => `%"${n}"%`), limit];
return this.db.prepare(query).all(...params);
}
getTemplate(templateId) {
const row = this.db.prepare(`
SELECT * FROM templates WHERE id = ?
`).get(templateId);
return row || null;
}
searchTemplates(query, limit = 20) {
logger_1.logger.debug(`Searching templates for: "${query}" (FTS5: ${this.hasFTS5Support})`);
if (!this.hasFTS5Support) {
logger_1.logger.debug('Using LIKE search (FTS5 not available)');
return this.searchTemplatesLIKE(query, limit);
}
try {
const ftsQuery = query.split(' ').map(term => {
const escaped = term.replace(/"/g, '""');
return `"${escaped}"`;
}).join(' OR ');
logger_1.logger.debug(`FTS5 query: ${ftsQuery}`);
const results = this.db.prepare(`
SELECT t.* FROM templates t
JOIN templates_fts ON t.id = templates_fts.rowid
WHERE templates_fts MATCH ?
ORDER BY rank, t.views DESC
LIMIT ?
`).all(ftsQuery, limit);
logger_1.logger.debug(`FTS5 search returned ${results.length} results`);
return results;
}
catch (error) {
logger_1.logger.warn('FTS5 template search failed, using LIKE fallback:', {
message: error.message,
query: query,
ftsQuery: query.split(' ').map(term => `"${term}"`).join(' OR ')
});
return this.searchTemplatesLIKE(query, limit);
}
}
searchTemplatesLIKE(query, limit = 20) {
const likeQuery = `%${query}%`;
logger_1.logger.debug(`Using LIKE search with pattern: ${likeQuery}`);
const results = this.db.prepare(`
SELECT * FROM templates
WHERE name LIKE ? OR description LIKE ?
ORDER BY views DESC, created_at DESC
LIMIT ?
`).all(likeQuery, likeQuery, limit);
logger_1.logger.debug(`LIKE search returned ${results.length} results`);
return results;
}
getTemplatesForTask(task) {
const taskNodeMap = {
'ai_automation': ['@n8n/n8n-nodes-langchain.openAi', '@n8n/n8n-nodes-langchain.agent', 'n8n-nodes-base.openAi'],
'data_sync': ['n8n-nodes-base.googleSheets', 'n8n-nodes-base.postgres', 'n8n-nodes-base.mysql'],
'webhook_processing': ['n8n-nodes-base.webhook', 'n8n-nodes-base.httpRequest'],
'email_automation': ['n8n-nodes-base.gmail', 'n8n-nodes-base.emailSend', 'n8n-nodes-base.emailReadImap'],
'slack_integration': ['n8n-nodes-base.slack', 'n8n-nodes-base.slackTrigger'],
'data_transformation': ['n8n-nodes-base.code', 'n8n-nodes-base.set', 'n8n-nodes-base.merge'],
'file_processing': ['n8n-nodes-base.readBinaryFile', 'n8n-nodes-base.writeBinaryFile', 'n8n-nodes-base.googleDrive'],
'scheduling': ['n8n-nodes-base.scheduleTrigger', 'n8n-nodes-base.cron'],
'api_integration': ['n8n-nodes-base.httpRequest', 'n8n-nodes-base.graphql'],
'database_operations': ['n8n-nodes-base.postgres', 'n8n-nodes-base.mysql', 'n8n-nodes-base.mongodb']
};
const nodes = taskNodeMap[task];
if (!nodes) {
return [];
}
return this.getTemplatesByNodes(nodes, 10);
}
getAllTemplates(limit = 10) {
return this.db.prepare(`
SELECT * FROM templates
ORDER BY views DESC, created_at DESC
LIMIT ?
`).all(limit);
}
getTemplateCount() {
const result = this.db.prepare('SELECT COUNT(*) as count FROM templates').get();
return result.count;
}
getTemplateStats() {
const count = this.getTemplateCount();
const avgViews = this.db.prepare('SELECT AVG(views) as avg FROM templates').get();
const topNodes = this.db.prepare(`
SELECT nodes_used FROM templates
ORDER BY views DESC
LIMIT 100
`).all();
const nodeCount = {};
topNodes.forEach(t => {
const nodes = JSON.parse(t.nodes_used);
nodes.forEach((n) => {
nodeCount[n] = (nodeCount[n] || 0) + 1;
});
});
const topUsedNodes = Object.entries(nodeCount)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([node, count]) => ({ node, count }));
return {
totalTemplates: count,
averageViews: Math.round(avgViews.avg || 0),
topUsedNodes
};
}
clearTemplates() {
this.db.exec('DELETE FROM templates');
logger_1.logger.info('Cleared all templates from database');
}
rebuildTemplateFTS() {
if (!this.hasFTS5Support) {
return;
}
try {
this.db.exec('DELETE FROM templates_fts');
this.db.exec(`
INSERT INTO templates_fts(rowid, name, description)
SELECT id, name, description FROM templates
`);
const count = this.getTemplateCount();
logger_1.logger.info(`Rebuilt FTS5 index for ${count} templates`);
}
catch (error) {
logger_1.logger.warn('Failed to rebuild template FTS5 index:', error);
}
}
}
exports.TemplateRepository = TemplateRepository;
//# sourceMappingURL=template-repository.js.map