n8n-nodes-actual-budget-category 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ const { createExpense, initActual, shutdownActual } = require('../actual/CreateExpense.cjs');
2
+
3
+ class ActualBudget {
4
+ async execute() {
5
+ const items = this.getInputData();
6
+ const creds = await this.getCredentials('actualBudgetApi');
7
+ const results = [];
8
+
9
+ // ✅ INIT ONCE
10
+ await initActual({
11
+ serverURL: creds.serverUrl,
12
+ password: creds.password,
13
+ syncId: creds.syncId,
14
+ });
15
+
16
+ try {
17
+ for (let i = 0; i < items.length; i++) {
18
+ const result = await createExpense({
19
+ amount: this.getNodeParameter('amount', i),
20
+ categoryName: this.getNodeParameter('categoryName', i),
21
+ accountName: this.getNodeParameter('accountName', i),
22
+ payee: this.getNodeParameter('payee', i),
23
+ notes: this.getNodeParameter('notes', i),
24
+ importedId: `n8n-${Date.now()}-${i}`,
25
+ });
26
+
27
+ results.push({ json: { success: true, ...result } });
28
+ }
29
+ } finally {
30
+ // ✅ SHUTDOWN ONCE
31
+ await shutdownActual();
32
+ }
33
+
34
+ return [results];
35
+ }
36
+ }
37
+
38
+ module.exports = { ActualBudget };
@@ -0,0 +1,32 @@
1
+ class ActualBudgetApi {
2
+ constructor() {
3
+ this.name = 'actualBudgetApi';
4
+ this.displayName = 'Actual Budget API';
5
+
6
+ this.properties = [
7
+ {
8
+ displayName: 'Server URL',
9
+ name: 'serverUrl',
10
+ type: 'string',
11
+ required: true,
12
+ placeholder: 'https://actual.example.com',
13
+ },
14
+ {
15
+ displayName: 'Password',
16
+ name: 'password',
17
+ type: 'string',
18
+ typeOptions: { password: true },
19
+ required: true,
20
+ },
21
+ {
22
+ displayName: 'Sync ID',
23
+ name: 'syncId',
24
+ type: 'string',
25
+ required: true,
26
+ description: 'Found in Settings → Advanced → Sync ID',
27
+ },
28
+ ];
29
+ }
30
+ }
31
+
32
+ module.exports = { ActualBudgetApi };
@@ -0,0 +1,108 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ const { init, shutdown, downloadBudget, getAccounts, getCategories, addTransactions } = require('@actual-app/api');
6
+
7
+ function normalize(s) {
8
+ return s.toLowerCase().trim();
9
+ }
10
+
11
+ class ActualBudget {
12
+ constructor() {
13
+ this.description = {
14
+ displayName: 'Actual Budget',
15
+ name: 'actualBudget',
16
+ group: ['finance'],
17
+ version: 1,
18
+ description: 'Create a transaction in Actual Budget',
19
+ defaults: { name: 'Actual Budget' },
20
+ inputs: ['main'],
21
+ outputs: ['main'],
22
+ credentials: [{ name: 'actualBudgetApi', required: true }],
23
+ properties: [
24
+ { displayName: 'Amount', name: 'amount', type: 'number', required: true },
25
+ { displayName: 'Category Name', name: 'categoryName', type: 'string', required: true },
26
+ { displayName: 'Account Name', name: 'accountName', type: 'string', required: true },
27
+ { displayName: 'Payee', name: 'payee', type: 'string' },
28
+ { displayName: 'Notes', name: 'notes', type: 'string' },
29
+ ],
30
+ };
31
+ }
32
+
33
+ async execute() {
34
+ const items = this.getInputData();
35
+ const creds = await this.getCredentials('actualBudgetApi');
36
+ const results = [];
37
+
38
+ // 🔑 FIX: ensure syncId is a string
39
+ const syncId = typeof creds.syncId === 'string' ? creds.syncId : creds.syncId?.value;
40
+
41
+ if (!syncId) {
42
+ throw new Error(`Invalid syncId: ${JSON.stringify(creds.syncId)}`);
43
+ }
44
+
45
+ // same behavior you already had
46
+ const dataDir = path.join(os.tmpdir(), 'actual-budget', syncId);
47
+ fs.mkdirSync(dataDir, { recursive: true });
48
+
49
+ // INIT ONCE
50
+ await init({
51
+ serverURL: creds.serverUrl,
52
+ password: creds.password,
53
+ dataDir,
54
+ });
55
+
56
+ try {
57
+ // open budget once
58
+ await downloadBudget({ syncId });
59
+
60
+ const accounts = await getAccounts();
61
+ const categories = await getCategories();
62
+
63
+ for (let i = 0; i < items.length; i++) {
64
+ const amount = this.getNodeParameter('amount', i);
65
+ const categoryName = this.getNodeParameter('categoryName', i);
66
+ const accountName = this.getNodeParameter('accountName', i);
67
+ const payee = this.getNodeParameter('payee', i);
68
+ const notes = this.getNodeParameter('notes', i);
69
+
70
+ const account = accounts.find(a => normalize(a.name) === normalize(accountName));
71
+ if (!account) {
72
+ throw new Error(`Account not found: ${accountName}`);
73
+ }
74
+
75
+ const category = categories.find(c => normalize(c.name) === normalize(categoryName));
76
+ if (!category) {
77
+ throw new Error(`Category not found: ${categoryName}`);
78
+ }
79
+
80
+ await addTransactions(account.id, [
81
+ {
82
+ amount: -Math.round(amount * 100),
83
+ category: category.id,
84
+ payee_name: payee,
85
+ notes: notes || '',
86
+ date: new Date().toISOString().slice(0, 10),
87
+ imported_id: `n8n-${Date.now()}-${i}`,
88
+ },
89
+ ]);
90
+
91
+ results.push({
92
+ json: {
93
+ success: true,
94
+ accountId: account.id,
95
+ categoryId: category.id,
96
+ },
97
+ });
98
+ }
99
+ } finally {
100
+ // SHUTDOWN ONCE
101
+ await shutdown();
102
+ }
103
+
104
+ return [results];
105
+ }
106
+ }
107
+
108
+ module.exports = { ActualBudget };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "n8n-nodes-actual-budget-category",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node for Actual Budget",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "n8n-community-node",
8
+ "actual-budget",
9
+ "finance"
10
+ ],
11
+ "author": "Sebastian Larrieu",
12
+ "main": "index.js",
13
+ "files": [
14
+ "nodes",
15
+ "credentials",
16
+ "actual"
17
+ ],
18
+ "scripts": {
19
+ "test": "node test/actual-budget-test.cjs"
20
+ },
21
+ "dependencies": {
22
+ "@actual-app/api": "^25.12.0"
23
+ },
24
+ "n8n": {
25
+ "nodes": [
26
+ "nodes/ActualBudget.node.js"
27
+ ],
28
+ "credentials": [
29
+ "credentials/ActualBudgetApi.credentials.js"
30
+ ]
31
+ }
32
+ }