UNPKG

n8n-nodes-fleeting-notes

Version:

n8n community node for managing fleeting notes in Obsidian vault

535 lines 24.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FleetingNotes = void 0; const n8n_workflow_1 = require("n8n-workflow"); const supabase_js_1 = require("@supabase/supabase-js"); const uuid_1 = require("uuid"); const crypto_js_1 = __importDefault(require("crypto-js")); const SUPABASE_URL = 'https://yixcweyqwkqyvebpmdvr.supabase.co'; const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlpeGN3ZXlxd2txeXZlYnBtZHZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjQ4MDMyMTgsImV4cCI6MTk4MDM3OTIxOH0.awfZKRuaLOPzniEJ2CIth8NWPYnelLfsWrMWH2Bz3w8'; class FleetingNotes { constructor() { this.description = { displayName: 'Fleeting Notes', name: 'fleetingNotes', icon: 'file:icon.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Create and manage Fleeting Notes', defaults: { name: 'Fleeting Notes', }, inputs: ['main'], outputs: ['main'], usableAsTool: true, credentials: [ { name: 'fleetingNotesApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Note', value: 'note', }, ], default: 'note', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['note'], }, }, options: [ { name: 'Create', value: 'create', description: 'Create a new note', action: 'Create a new note', }, { name: 'Delete', value: 'delete', description: 'Delete a note', action: 'Delete a note', }, { name: 'Get', value: 'get', description: 'Get a note', action: 'Get a note', }, { name: 'Get Many', value: 'getAll', description: 'Get many notes', action: 'Get many notes', }, { name: 'Search', value: 'search', description: 'Search notes by keyword', action: 'Search notes', }, { name: 'Update', value: 'update', description: 'Update a note', action: 'Update a note', }, ], default: 'create', }, { displayName: 'Title', name: 'title', type: 'string', required: true, default: '', displayOptions: { show: { resource: ['note'], operation: ['create'], }, }, description: 'Title of the note', }, { displayName: 'Content', name: 'content', type: 'string', typeOptions: { alwaysOpenEditWindow: true, }, required: true, default: '', displayOptions: { show: { resource: ['note'], operation: ['create'], }, }, description: 'Content of the note', }, { displayName: 'Note ID', name: 'noteId', type: 'string', required: true, default: '', displayOptions: { show: { resource: ['note'], operation: ['get', 'delete', 'update'], }, }, description: 'ID of the note', }, { displayName: 'Return All', name: 'returnAll', type: 'boolean', displayOptions: { show: { resource: ['note'], operation: ['getAll'], }, }, default: false, description: 'Whether to return all results or only up to a given limit', }, { displayName: 'Limit', name: 'limit', type: 'number', displayOptions: { show: { resource: ['note'], operation: ['getAll'], returnAll: [false], }, }, typeOptions: { minValue: 1, }, default: 50, description: 'Max number of results to return', }, { displayName: 'Search Query', name: 'searchQuery', type: 'string', required: true, default: '', displayOptions: { show: { resource: ['note'], operation: ['search'], }, }, description: 'Keyword to search in note titles and content', }, { displayName: 'Search Mode', name: 'searchMode', type: 'options', options: [ { name: 'Client-Side (Works With Encryption)', value: 'client' }, { name: 'Server-Side (Faster, No Encryption)', value: 'server' }, ], default: 'server', displayOptions: { show: { resource: ['note'], operation: ['search'], }, }, description: 'Search mode to use', }, { displayName: 'Return All', name: 'returnAll', type: 'boolean', displayOptions: { show: { resource: ['note'], operation: ['search'], }, }, default: false, description: 'Whether to return all results or only up to a given limit', }, { displayName: 'Limit', name: 'limit', type: 'number', displayOptions: { show: { resource: ['note'], operation: ['search'], returnAll: [false], }, }, typeOptions: { minValue: 1, }, default: 50, description: 'Max number of results to return', }, { displayName: 'Update Fields', name: 'updateFields', type: 'collection', placeholder: 'Add Field', default: {}, displayOptions: { show: { resource: ['note'], operation: ['update'], }, }, options: [ { displayName: 'Title', name: 'title', type: 'string', default: '', description: 'New title of the note', }, { displayName: 'Content', name: 'content', type: 'string', typeOptions: { alwaysOpenEditWindow: true, }, default: '', description: 'New content of the note', }, ], }, ], }; } async execute() { var _a, _b; const items = this.getInputData(); const returnData = []; let supabase; const credentials = await this.getCredentials('fleetingNotesApi'); const email = credentials.email; const password = credentials.password; const encryptionKey = credentials.encryptionKey; if (!email || !password) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Missing required credentials'); } try { supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_KEY); const { data: authData, error: authError } = await supabase.auth.signInWithPassword({ email, password, }); if (authError) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Authentication failed: ' + authError.message); } if (!((_a = authData === null || authData === void 0 ? void 0 : authData.user) === null || _a === void 0 ? void 0 : _a.id)) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to get user ID after authentication'); } } catch (error) { if (error instanceof Error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Supabase client: ' + error.message); } throw error; } const encryptNote = (note) => { if (!encryptionKey) return note; try { const encrypted = { ...note, encrypted: true }; if (encrypted.title) { encrypted.title = crypto_js_1.default.AES.encrypt(encrypted.title, encryptionKey).toString(); } if (encrypted.content) { encrypted.content = crypto_js_1.default.AES.encrypt(encrypted.content, encryptionKey).toString(); } return encrypted; } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to encrypt note: ' + (error instanceof Error ? error.message : 'Unknown error')); } }; const decryptNote = (note) => { if (!note.encrypted || !encryptionKey) return note; try { const decrypted = { ...note, encrypted: false }; if (decrypted.title) { const decryptedTitle = crypto_js_1.default.AES.decrypt(decrypted.title, encryptionKey).toString(crypto_js_1.default.enc.Utf8); if (!decryptedTitle) { throw new Error('Failed to decrypt title - invalid encryption key?'); } decrypted.title = decryptedTitle; } if (decrypted.content) { const decryptedContent = crypto_js_1.default.AES.decrypt(decrypted.content, encryptionKey).toString(crypto_js_1.default.enc.Utf8); if (!decryptedContent) { throw new Error('Failed to decrypt content - invalid encryption key?'); } decrypted.content = decryptedContent; } return decrypted; } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to decrypt note: ' + (error instanceof Error ? error.message : 'Unknown error')); } }; for (let i = 0; i < items.length; i++) { try { const resource = this.getNodeParameter('resource', i); const operation = this.getNodeParameter('operation', i); if (resource === 'note') { if (operation === 'create') { const title = this.getNodeParameter('title', i); const content = this.getNodeParameter('content', i); const user = await supabase.auth.getUser(); if (!title.trim()) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Title cannot be empty'); } const note = { id: (0, uuid_1.v4)(), title, content, created_at: new Date().toISOString(), modified_at: new Date().toISOString(), deleted: false, _partition: (_b = user.data.user) === null || _b === void 0 ? void 0 : _b.id, }; const encryptedNote = encryptNote(note); const { data, error } = await supabase .from('notes') .insert([encryptedNote]) .select() .single(); if (error) throw new Error(error.message); if (!data) throw new Error('Failed to create note'); returnData.push({ json: decryptNote(data) }); } else if (operation === 'delete') { const noteId = this.getNodeParameter('noteId', i); if (!noteId.trim()) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Note ID cannot be empty'); } const { error } = await supabase .from('notes') .update({ deleted: true, modified_at: new Date().toISOString() }) .eq('id', noteId); if (error) throw new Error(error.message); returnData.push({ json: { success: true, id: noteId } }); } else if (operation === 'get') { const noteId = this.getNodeParameter('noteId', i); if (!noteId.trim()) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Note ID cannot be empty'); } const { data, error } = await supabase .from('notes') .select() .eq('id', noteId) .eq('deleted', false) .single(); if (error) { if (error.code === 'PGRST116') { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Note not found'); } throw new Error(error.message); } if (!data) throw new Error('Note not found'); returnData.push({ json: decryptNote(data) }); } else if (operation === 'getAll') { const returnAll = this.getNodeParameter('returnAll', i); let query = supabase .from('notes') .select() .eq('deleted', false) .order('modified_at', { ascending: false }); if (!returnAll) { const limit = this.getNodeParameter('limit', i); query = query.limit(limit); } const { data, error } = await query; if (error) throw new Error(error.message); if (!data) throw new Error('Failed to fetch notes'); for (const note of data) { returnData.push({ json: decryptNote(note) }); } } else if (operation === 'search') { const searchQuery = this.getNodeParameter('searchQuery', i); const searchMode = this.getNodeParameter('searchMode', i); const returnAll = this.getNodeParameter('returnAll', i); if (!searchQuery.trim()) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Search query cannot be empty'); } let matchingNotes = []; if (searchMode === 'server') { if (encryptionKey) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Server-side search does not support encrypted notes. Please use client-side search mode.'); } let query = supabase .from('notes') .select() .eq('deleted', false) .or(`title.ilike.%${searchQuery}%,content.ilike.%${searchQuery}%`) .order('modified_at', { ascending: false }); if (!returnAll) { const limit = this.getNodeParameter('limit', i); query = query.limit(limit); } const { data, error } = await query; if (error) throw new Error(error.message); if (!data) throw new Error('Failed to fetch notes'); matchingNotes = data; } else { const { data, error } = await supabase .from('notes') .select() .eq('deleted', false) .order('modified_at', { ascending: false }); if (error) throw new Error(error.message); if (!data) throw new Error('Failed to fetch notes'); matchingNotes = data .map((note) => decryptNote(note)) .filter((note) => { var _a, _b; const lowercaseQuery = searchQuery.toLowerCase(); return (((_a = note.title) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(lowercaseQuery)) || false || ((_b = note.content) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes(lowercaseQuery)) || false); }); if (!returnAll) { const limit = this.getNodeParameter('limit', i); matchingNotes = matchingNotes.slice(0, limit); } } for (const note of matchingNotes) { returnData.push({ json: note }); } } else if (operation === 'update') { const noteId = this.getNodeParameter('noteId', i); const updateFields = this.getNodeParameter('updateFields', i); if (!noteId.trim()) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Note ID cannot be empty'); } if (Object.keys(updateFields).length === 0) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'At least one field must be provided for update'); } const updateData = { ...updateFields, modified_at: new Date().toISOString(), }; const encryptedFields = encryptNote(updateData); const { data, error } = await supabase .from('notes') .update(encryptedFields) .eq('id', noteId) .eq('deleted', false) .select() .single(); if (error) { if (error.code === 'PGRST116') { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Note not found'); } throw new Error(error.message); } if (!data) throw new Error('Failed to update note'); returnData.push({ json: decryptNote(data) }); } } } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error instanceof Error ? error.message : 'Unknown error' }, }); continue; } throw error; } } return [returnData]; } } exports.FleetingNotes = FleetingNotes; //# sourceMappingURL=FleetingNotes.node.js.map