feat(properties): prepare property commands before execution
Build and Push Docker Container / build-and-push (push) Successful in 4m28s

- Create missing note files before running property:set.
- Default date-only datetime values to midnight.
- Keep property names out of path authorization checks.
- Split policy, vault, daily note, and command target helpers.
- Split n8n node args, description, output, and API response helpers.
- Split API tests by auth, command, daily note, and property behavior.
- Bump API and n8n node versions.
This commit is contained in:
2026-06-24 14:29:48 +02:00
parent e8e31add11
commit c34dab2cca
25 changed files with 1099 additions and 1018 deletions
@@ -0,0 +1,119 @@
import type { IExecuteFunctions } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { compactArgs, kv } from '../../shared/ObsidianApiClient';
export function buildArgs(context: IExecuteFunctions, resource: string, operation: string, itemIndex: number, additionalFields: Record<string, unknown>): string[] {
switch (resource) {
case 'note':
return buildNoteArgs(context, operation, itemIndex);
case 'daily':
return buildDailyArgs(context, operation, itemIndex);
case 'search':
return buildSearchArgs(context, operation, itemIndex);
case 'property':
return buildPropertyArgs(context, operation, itemIndex);
case 'vault':
return buildVaultArgs(context, operation, itemIndex, additionalFields);
case 'raw':
return parseRawArgs(context, itemIndex);
default:
throw new NodeOperationError(context.getNode(), `Unsupported resource: ${resource}`, { itemIndex });
}
}
function buildNoteArgs(context: IExecuteFunctions, operation: string, itemIndex: number): string[] {
const path = context.getNodeParameter('path', itemIndex, '') as string;
switch (operation) {
case 'create':
return compactArgs(['create', kv('path', path), kv('content', context.getNodeParameter('content', itemIndex) as string), kv('overwrite', context.getNodeParameter('overwrite', itemIndex) as boolean)]);
case 'read':
return compactArgs(['read', kv('path', path)]);
case 'append':
return compactArgs(['append', kv('path', path), kv('content', context.getNodeParameter('content', itemIndex) as string)]);
case 'prepend':
return compactArgs(['prepend', kv('path', path), kv('content', context.getNodeParameter('content', itemIndex) as string)]);
case 'delete':
return compactArgs(['delete', kv('path', path)]);
case 'rename':
return compactArgs(['rename', kv('path', path), kv('name', context.getNodeParameter('newName', itemIndex) as string)]);
case 'files':
return compactArgs(['files', kv('folder', context.getNodeParameter('folder', itemIndex, '') as string)]);
default:
throw new NodeOperationError(context.getNode(), `Unsupported note operation: ${operation}`, { itemIndex });
}
}
function buildDailyArgs(context: IExecuteFunctions, operation: string, itemIndex: number): string[] {
switch (operation) {
case 'dailyAppend':
return compactArgs(['daily:append', kv('content', context.getNodeParameter('dailyContent', itemIndex) as string)]);
case 'dailyPrepend':
return compactArgs(['daily:prepend', kv('content', context.getNodeParameter('dailyContent', itemIndex) as string)]);
case 'dailyRead':
return ['daily:read'];
case 'dailyPath':
return ['daily:path'];
default:
throw new NodeOperationError(context.getNode(), `Unsupported daily operation: ${operation}`, { itemIndex });
}
}
function buildSearchArgs(context: IExecuteFunctions, operation: string, itemIndex: number): string[] {
const command = operation === 'searchContext' ? 'search:context' : 'search';
return compactArgs([
command,
kv('query', context.getNodeParameter('query', itemIndex) as string),
kv('format', context.getNodeParameter('format', itemIndex) as string),
]);
}
function buildPropertyArgs(context: IExecuteFunctions, operation: string, itemIndex: number): string[] {
const path = context.getNodeParameter('propertyPath', itemIndex, '') as string;
const name = context.getNodeParameter('propertyName', itemIndex, '') as string;
switch (operation) {
case 'properties':
return compactArgs([
'properties',
kv('name', name),
kv('format', context.getNodeParameter('propertiesFormat', itemIndex) as string),
]);
case 'propertyRead':
return compactArgs(['property:read', kv('path', path), kv('name', name)]);
case 'propertyRemove':
return compactArgs(['property:remove', kv('path', path), kv('name', name)]);
case 'propertySet':
return compactArgs([
'property:set',
kv('path', path),
kv('name', name),
kv('value', context.getNodeParameter('propertyValue', itemIndex) as string),
kv('type', context.getNodeParameter('propertyType', itemIndex) as string),
]);
default:
throw new NodeOperationError(context.getNode(), `Unsupported property operation: ${operation}`, { itemIndex });
}
}
function buildVaultArgs(context: IExecuteFunctions, operation: string, itemIndex: number, additionalFields: Record<string, unknown>): string[] {
switch (operation) {
case 'vaultInfo':
return ['vault'];
case 'vaults': {
const verbose = additionalFields.verbose === undefined ? true : Boolean(additionalFields.verbose);
return compactArgs(['vaults', kv('verbose', verbose)]);
}
default:
throw new NodeOperationError(context.getNode(), `Unsupported vault operation: ${operation}`, { itemIndex });
}
}
function parseRawArgs(context: IExecuteFunctions, itemIndex: number): string[] {
const raw = context.getNodeParameter('rawArgs', itemIndex) as string | string[];
const parsed = Array.isArray(raw) ? raw : JSON.parse(raw);
if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === 'string')) {
throw new NodeOperationError(context.getNode(), 'Args JSON must be an array of strings', { itemIndex });
}
return parsed;
}