@lunchflow/actual-flow 0.0.1
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.
- package/README.md +196 -0
- package/dist/actual-budget-client.d.ts +19 -0
- package/dist/actual-budget-client.d.ts.map +1 -0
- package/dist/actual-budget-client.js +161 -0
- package/dist/actual-budget-client.js.map +1 -0
- package/dist/config-manager.d.ts +16 -0
- package/dist/config-manager.d.ts.map +1 -0
- package/dist/config-manager.js +119 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/importer.d.ts +20 -0
- package/dist/importer.d.ts.map +1 -0
- package/dist/importer.js +234 -0
- package/dist/importer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lunch-flow-client.d.ts +10 -0
- package/dist/lunch-flow-client.d.ts.map +1 -0
- package/dist/lunch-flow-client.js +67 -0
- package/dist/lunch-flow-client.js.map +1 -0
- package/dist/transaction-mapper.d.ts +8 -0
- package/dist/transaction-mapper.d.ts.map +1 -0
- package/dist/transaction-mapper.js +32 -0
- package/dist/transaction-mapper.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +30 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +278 -0
- package/dist/ui.js.map +1 -0
- package/install.sh +55 -0
- package/package.json +35 -0
- package/pnpm-workspace.yaml +2 -0
- package/src/actual-budget-client.ts +132 -0
- package/src/config-manager.ts +128 -0
- package/src/importer.ts +279 -0
- package/src/index.ts +28 -0
- package/src/lunch-flow-client.ts +64 -0
- package/src/transaction-mapper.ts +37 -0
- package/src/types.ts +60 -0
- package/src/ui.ts +314 -0
- package/tsconfig.json +19 -0
package/src/importer.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { LunchFlowClient } from './lunch-flow-client';
|
|
2
|
+
import { ActualBudgetClient } from './actual-budget-client';
|
|
3
|
+
import { TransactionMapper } from './transaction-mapper';
|
|
4
|
+
import { ConfigManager } from './config-manager';
|
|
5
|
+
import { TerminalUI } from './ui';
|
|
6
|
+
import { Config, AccountMapping, ConnectionStatus } from './types';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import Table from 'cli-table3';
|
|
9
|
+
|
|
10
|
+
export class LunchFlowImporter {
|
|
11
|
+
private lfClient: LunchFlowClient;
|
|
12
|
+
private abClient: ActualBudgetClient;
|
|
13
|
+
private configManager: ConfigManager;
|
|
14
|
+
private ui: TerminalUI;
|
|
15
|
+
private config: Config | null = null;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.configManager = new ConfigManager();
|
|
19
|
+
this.ui = new TerminalUI();
|
|
20
|
+
this.config = this.configManager.loadConfig();
|
|
21
|
+
|
|
22
|
+
// Initialize clients with default values, will be updated when config is loaded
|
|
23
|
+
this.lfClient = new LunchFlowClient('', '');
|
|
24
|
+
this.abClient = new ActualBudgetClient('', '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async initialize(): Promise<void> {
|
|
28
|
+
await this.ui.showWelcome();
|
|
29
|
+
|
|
30
|
+
if (!this.config || !this.configManager.isConfigured()) {
|
|
31
|
+
console.log('No configuration found or incomplete. Let\'s set it up!\n');
|
|
32
|
+
await this.setupConfiguration();
|
|
33
|
+
} else {
|
|
34
|
+
this.updateClients();
|
|
35
|
+
this.ui.showInfo('Configuration loaded successfully');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private async setupConfiguration(): Promise<void> {
|
|
40
|
+
const lfCreds = await this.ui.getLunchFlowCredentials();
|
|
41
|
+
const abCreds = await this.ui.getActualBudgetCredentials();
|
|
42
|
+
|
|
43
|
+
this.config = {
|
|
44
|
+
lunchFlow: lfCreds,
|
|
45
|
+
actualBudget: abCreds,
|
|
46
|
+
accountMappings: [],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.configManager.saveConfig(this.config);
|
|
50
|
+
this.updateClients();
|
|
51
|
+
this.ui.showSuccess('Configuration saved successfully');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private updateClients(): void {
|
|
55
|
+
if (this.config) {
|
|
56
|
+
this.lfClient = new LunchFlowClient(
|
|
57
|
+
this.config.lunchFlow.apiKey,
|
|
58
|
+
this.config.lunchFlow.baseUrl
|
|
59
|
+
);
|
|
60
|
+
this.abClient = new ActualBudgetClient(
|
|
61
|
+
this.config.actualBudget.serverUrl,
|
|
62
|
+
this.config.actualBudget.budgetSyncId,
|
|
63
|
+
this.config.actualBudget.password
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async testConnections(): Promise<ConnectionStatus> {
|
|
69
|
+
const spinner = this.ui.showSpinner('Testing connections...');
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const [lfConnected, abConnected] = await Promise.all([
|
|
73
|
+
this.lfClient.testConnection(),
|
|
74
|
+
this.abClient.testConnection(),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
spinner.stop();
|
|
78
|
+
await this.ui.showConnectionStatus({ lunchFlow: lfConnected, actualBudget: abConnected });
|
|
79
|
+
|
|
80
|
+
return { lunchFlow: lfConnected, actualBudget: abConnected };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
spinner.stop();
|
|
83
|
+
this.ui.showError('Failed to test connections');
|
|
84
|
+
return { lunchFlow: false, actualBudget: false };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async configureAccountMappings(): Promise<void> {
|
|
89
|
+
const spinner = this.ui.showSpinner('Loading accounts...');
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const [lfAccounts, abAccounts] = await Promise.all([
|
|
93
|
+
this.lfClient.getAccounts(),
|
|
94
|
+
this.abClient.getAccounts(),
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
spinner.stop();
|
|
98
|
+
|
|
99
|
+
if (lfAccounts.length === 0) {
|
|
100
|
+
this.ui.showError('No Lunch Flow accounts found');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (abAccounts.length === 0) {
|
|
105
|
+
this.ui.showError('No Actual Budget accounts found');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Show accounts for reference
|
|
110
|
+
await this.ui.showAccountsTable(lfAccounts, '📡 Lunch Flow Accounts');
|
|
111
|
+
await this.ui.showAccountsTable(abAccounts, '💰 Actual Budget Accounts');
|
|
112
|
+
|
|
113
|
+
const mappings = await this.ui.configureAccountMappings(lfAccounts, abAccounts);
|
|
114
|
+
|
|
115
|
+
if (this.config) {
|
|
116
|
+
this.config.accountMappings = mappings;
|
|
117
|
+
this.configManager.saveConfig(this.config);
|
|
118
|
+
this.ui.showSuccess(`Configured ${mappings.length} account mappings`);
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
spinner.stop();
|
|
122
|
+
this.ui.showError('Failed to configure account mappings');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async importTransactions(): Promise<void> {
|
|
127
|
+
if (!this.config || this.config.accountMappings.length === 0) {
|
|
128
|
+
this.ui.showError('No account mappings configured. Please configure mappings first.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Test connections first
|
|
133
|
+
const status = await this.testConnections();
|
|
134
|
+
if (!status.lunchFlow || !status.actualBudget) {
|
|
135
|
+
this.ui.showError('Cannot import: connections failed');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const spinner = this.ui.showSpinner('Fetching transactions...');
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const lfTransactions = await this.lfClient.getTransactions(this.config.accountMappings[0].lunchFlowAccountId);
|
|
143
|
+
spinner.stop();
|
|
144
|
+
|
|
145
|
+
if (lfTransactions.length === 0) {
|
|
146
|
+
this.ui.showInfo('No transactions found for the selected date range');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const mapper = new TransactionMapper(this.config.accountMappings);
|
|
151
|
+
const abTransactions = mapper.mapTransactions(lfTransactions);
|
|
152
|
+
|
|
153
|
+
if (abTransactions.length === 0) {
|
|
154
|
+
this.ui.showError('No transactions could be mapped to Actual Budget accounts');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const startDate = abTransactions.reduce((min, t) => t.date < min ? t.date : min, abTransactions[0].date);
|
|
159
|
+
const endDate = abTransactions.reduce((max, t) => t.date > max ? t.date : max, abTransactions[0].date);
|
|
160
|
+
|
|
161
|
+
// Show preview
|
|
162
|
+
const abAccounts = await this.abClient.getAccounts();
|
|
163
|
+
await this.ui.showTransactionPreview(abTransactions, abAccounts);
|
|
164
|
+
|
|
165
|
+
const confirmed = await this.ui.confirmImport(abTransactions.length, { startDate, endDate });
|
|
166
|
+
if (!confirmed) {
|
|
167
|
+
this.ui.showInfo('Import cancelled');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const importSpinner = this.ui.showSpinner(`Importing ${abTransactions.length} transactions...`);
|
|
172
|
+
await this.abClient.importTransactions(abTransactions);
|
|
173
|
+
importSpinner.stop();
|
|
174
|
+
|
|
175
|
+
this.ui.showSuccess(`Successfully imported ${abTransactions.length} transactions`);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
spinner.stop();
|
|
178
|
+
this.ui.showError('Failed to import transactions');
|
|
179
|
+
console.error('Import error:', error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async showCurrentMappings(): Promise<void> {
|
|
184
|
+
if (this.config) {
|
|
185
|
+
await this.ui.showAccountMappings(this.config.accountMappings);
|
|
186
|
+
} else {
|
|
187
|
+
this.ui.showError('No configuration found');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async listAvailableBudgets(): Promise<void> {
|
|
192
|
+
const spinner = this.ui.showSpinner('Fetching available budgets...');
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const budgets = await this.abClient.listAvailableBudgets();
|
|
196
|
+
spinner.stop();
|
|
197
|
+
|
|
198
|
+
if (budgets.length === 0) {
|
|
199
|
+
this.ui.showWarning('No budgets found on the server');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(chalk.blue('\n📋 Available Budgets\n'));
|
|
204
|
+
const table = new Table({
|
|
205
|
+
head: ['Name', 'ID'],
|
|
206
|
+
colWidths: [30, 40],
|
|
207
|
+
style: {
|
|
208
|
+
head: ['cyan'],
|
|
209
|
+
border: ['gray'],
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
budgets.forEach(budget => {
|
|
214
|
+
table.push([budget.name, budget.id]);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
console.log(table.toString());
|
|
218
|
+
} catch (error) {
|
|
219
|
+
spinner.stop();
|
|
220
|
+
this.ui.showError('Failed to fetch available budgets');
|
|
221
|
+
console.error('Error:', error);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async reconfigureCredentials(): Promise<void> {
|
|
226
|
+
const action = await this.ui.showReconfigureMenu();
|
|
227
|
+
|
|
228
|
+
if (action === 'cancel') return;
|
|
229
|
+
|
|
230
|
+
if (action === 'lunchflow' || action === 'both') {
|
|
231
|
+
const lfCreds = await this.ui.getLunchFlowCredentials();
|
|
232
|
+
this.configManager.updateLunchFlowConfig(lfCreds.apiKey, lfCreds.baseUrl);
|
|
233
|
+
this.ui.showSuccess('Lunch Flow credentials updated');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (action === 'actualbudget' || action === 'both') {
|
|
237
|
+
const abCreds = await this.ui.getActualBudgetCredentials();
|
|
238
|
+
this.configManager.updateActualBudgetConfig(abCreds.serverUrl, abCreds.budgetSyncId, abCreds.password);
|
|
239
|
+
this.ui.showSuccess('Actual Budget credentials updated');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Reload config and update clients
|
|
243
|
+
this.config = this.configManager.loadConfig();
|
|
244
|
+
this.updateClients();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async run(): Promise<void> {
|
|
248
|
+
await this.initialize();
|
|
249
|
+
|
|
250
|
+
while (true) {
|
|
251
|
+
const action = await this.ui.showMainMenu();
|
|
252
|
+
|
|
253
|
+
switch (action) {
|
|
254
|
+
case 'test':
|
|
255
|
+
await this.testConnections();
|
|
256
|
+
break;
|
|
257
|
+
case 'list-budgets':
|
|
258
|
+
await this.listAvailableBudgets();
|
|
259
|
+
break;
|
|
260
|
+
case 'configure':
|
|
261
|
+
await this.configureAccountMappings();
|
|
262
|
+
break;
|
|
263
|
+
case 'show':
|
|
264
|
+
await this.showCurrentMappings();
|
|
265
|
+
break;
|
|
266
|
+
case 'import':
|
|
267
|
+
await this.importTransactions();
|
|
268
|
+
break;
|
|
269
|
+
case 'reconfigure':
|
|
270
|
+
await this.reconfigureCredentials();
|
|
271
|
+
break;
|
|
272
|
+
case 'exit':
|
|
273
|
+
console.log(chalk.blue('\n👋 Goodbye!\n'));
|
|
274
|
+
await this.abClient.shutdown();
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { LunchFlowImporter } from './importer';
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
try {
|
|
9
|
+
const importer = new LunchFlowImporter();
|
|
10
|
+
await importer.run();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(chalk.red('An error occurred:'), error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Handle uncaught exceptions
|
|
18
|
+
process.on('uncaughtException', (error) => {
|
|
19
|
+
console.error(chalk.red('Uncaught Exception:'), error);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
24
|
+
console.error(chalk.red('Unhandled Rejection at:'), promise, 'reason:', reason);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
main();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import { LunchFlowTransaction, LunchFlowAccount, LunchFlowAccountId } from './types';
|
|
3
|
+
|
|
4
|
+
export class LunchFlowClient {
|
|
5
|
+
private client: AxiosInstance;
|
|
6
|
+
private apiKey: string;
|
|
7
|
+
|
|
8
|
+
constructor(apiKey: string, baseUrl: string = 'https://api.lunchflow.com') {
|
|
9
|
+
this.apiKey = apiKey;
|
|
10
|
+
this.client = axios.create({
|
|
11
|
+
baseURL: baseUrl,
|
|
12
|
+
headers: {
|
|
13
|
+
'x-api-key': apiKey,
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
},
|
|
16
|
+
timeout: 10000,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async testConnection(): Promise<boolean> {
|
|
21
|
+
try {
|
|
22
|
+
// Try to get accounts as a health check
|
|
23
|
+
const response = await this.client.get('/accounts');
|
|
24
|
+
return response.status === 200;
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
console.error('Lunch Flow connection test failed:', error.message);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getAccounts(): Promise<LunchFlowAccount[]> {
|
|
32
|
+
try {
|
|
33
|
+
const response = await this.client.get('/accounts');
|
|
34
|
+
|
|
35
|
+
// Handle different possible response structures
|
|
36
|
+
if (Array.isArray(response.data.accounts)) {
|
|
37
|
+
return response.data.accounts;
|
|
38
|
+
} else {
|
|
39
|
+
console.warn('Unexpected response structure from Lunch Flow accounts endpoint');
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
console.error('Failed to fetch Lunch Flow accounts:', error.message);
|
|
44
|
+
throw new Error(`Failed to fetch accounts: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getTransactions(accountId: LunchFlowAccountId): Promise<LunchFlowTransaction[]> {
|
|
49
|
+
try {
|
|
50
|
+
const response = await this.client.get(`/accounts/${accountId}/transactions`);
|
|
51
|
+
|
|
52
|
+
// Handle different possible response structures
|
|
53
|
+
if (Array.isArray(response.data.transactions)) {
|
|
54
|
+
return response.data.transactions;
|
|
55
|
+
} else {
|
|
56
|
+
console.warn('Unexpected response structure from Lunch Flow transactions endpoint');
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
console.error('Failed to fetch Lunch Flow transactions:', error.message);
|
|
61
|
+
throw new Error(`Failed to fetch transactions: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LunchFlowTransaction, ActualBudgetTransaction, AccountMapping } from './types';
|
|
2
|
+
|
|
3
|
+
export class TransactionMapper {
|
|
4
|
+
private accountMappings: AccountMapping[];
|
|
5
|
+
|
|
6
|
+
constructor(accountMappings: AccountMapping[]) {
|
|
7
|
+
this.accountMappings = accountMappings;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
mapTransaction(lfTransaction: LunchFlowTransaction): ActualBudgetTransaction | null {
|
|
11
|
+
const mapping = this.accountMappings.find(
|
|
12
|
+
m => m.lunchFlowAccountId === lfTransaction.accountId
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
if (!mapping) {
|
|
16
|
+
console.warn(`No mapping found for Lunch Flow account ${lfTransaction.accountId}`);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
date: lfTransaction.date,
|
|
22
|
+
// Forcing to fixed point integer to avoid floating point precision issues
|
|
23
|
+
amount: parseInt((lfTransaction.amount * 100).toFixed(0)),
|
|
24
|
+
imported_payee: lfTransaction.merchant,
|
|
25
|
+
account: mapping.actualBudgetAccountId,
|
|
26
|
+
cleared: true, // Lunch Flow transactions are always cleared
|
|
27
|
+
notes: lfTransaction.description,
|
|
28
|
+
imported_id: `lf_${lfTransaction.id}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
mapTransactions(lfTransactions: LunchFlowTransaction[]): ActualBudgetTransaction[] {
|
|
33
|
+
return lfTransactions
|
|
34
|
+
.map(t => this.mapTransaction(t))
|
|
35
|
+
.filter((t): t is ActualBudgetTransaction => t !== null);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type LunchFlowAccountId = number;
|
|
2
|
+
export interface LunchFlowTransaction {
|
|
3
|
+
id: string;
|
|
4
|
+
accountId: LunchFlowAccountId;
|
|
5
|
+
date: string;
|
|
6
|
+
amount: number;
|
|
7
|
+
currency: string;
|
|
8
|
+
merchant: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LunchFlowAccount {
|
|
13
|
+
id: LunchFlowAccountId;
|
|
14
|
+
name: string;
|
|
15
|
+
institution_name: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ActualBudgetTransaction {
|
|
19
|
+
id?: string;
|
|
20
|
+
date: string;
|
|
21
|
+
amount: number;
|
|
22
|
+
imported_payee: string;
|
|
23
|
+
account: string;
|
|
24
|
+
cleared?: boolean;
|
|
25
|
+
notes?: string;
|
|
26
|
+
imported_id?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ActualBudgetAccount {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
type: 'checking' | 'savings' | 'credit' | 'investment' | 'other';
|
|
33
|
+
balance: number;
|
|
34
|
+
currency: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AccountMapping {
|
|
38
|
+
lunchFlowAccountId: LunchFlowAccountId;
|
|
39
|
+
lunchFlowAccountName: string;
|
|
40
|
+
actualBudgetAccountId: string;
|
|
41
|
+
actualBudgetAccountName: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Config {
|
|
45
|
+
lunchFlow: {
|
|
46
|
+
apiKey: string;
|
|
47
|
+
baseUrl: string;
|
|
48
|
+
};
|
|
49
|
+
actualBudget: {
|
|
50
|
+
serverUrl: string;
|
|
51
|
+
budgetSyncId: string;
|
|
52
|
+
password?: string;
|
|
53
|
+
};
|
|
54
|
+
accountMappings: AccountMapping[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ConnectionStatus {
|
|
58
|
+
lunchFlow: boolean;
|
|
59
|
+
actualBudget: boolean;
|
|
60
|
+
}
|