n8n-nodes-fleeting-notes
Version:
n8n community node for managing fleeting notes in Obsidian vault
535 lines • 24.2 kB
JavaScript
"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