@actual-app/sync-server 25.4.0-alpha.0 → 25.4.0-alpha.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/LICENSE +7 -0
- package/README.md +41 -0
- package/bin/actual-server.js +85 -0
- package/package.json +14 -3
- package/src/app-gocardless/README.md +198 -198
- package/src/app-gocardless/link.html +18 -18
- package/src/sql/messages.sql +9 -9
- package/.dockerignore +0 -12
- package/babel.config.json +0 -3
- package/bin/@actual-app/sync-server +0 -55
- package/docker/alpine.Dockerfile +0 -62
- package/docker/ubuntu.Dockerfile +0 -63
- package/docker-compose.yml +0 -29
- package/jest.config.json +0 -19
- package/jest.global-setup.js +0 -101
- package/jest.global-teardown.js +0 -6
- package/tsconfig.json +0 -21
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright James Long
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,6 +8,47 @@ If you are interested in contributing, or want to know how development works, se
|
|
|
8
8
|
|
|
9
9
|
Want to say thanks? Click the ⭐ at the top of the page.
|
|
10
10
|
|
|
11
|
+
### Using the CLI tool
|
|
12
|
+
|
|
13
|
+
Node.js v18 or higher is required for the @actual-app/sync-server npm package
|
|
14
|
+
|
|
15
|
+
**Install globally with npm:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install --location=global @actual-app/sync-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After installing, you can execute actual-server commands directly in your terminal.
|
|
22
|
+
|
|
23
|
+
**Usage**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
actual-server [options]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Available options**
|
|
30
|
+
|
|
31
|
+
| Command | Description |
|
|
32
|
+
| ------------- |-------------|
|
|
33
|
+
|`-h` or `--help` |Print this list and exit. |
|
|
34
|
+
|`-v` or `--version` |Print this version and exit. |
|
|
35
|
+
|`--config` |Path to the config file. |
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
**Examples**
|
|
39
|
+
|
|
40
|
+
Run with default configuration
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
actual-server
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Runs with custom configuration
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
actual-server --config ./config.json
|
|
50
|
+
```
|
|
51
|
+
|
|
11
52
|
### Documentation
|
|
12
53
|
|
|
13
54
|
We have a wide range of documentation on how to use Actual. This is all available in our [Community Documentation](https://actualbudget.org/docs/), including topics on [installing](https://actualbudget.org/docs/install/), [Budgeting](https://actualbudget.org/docs/budgeting/), [Account Management](https://actualbudget.org/docs/accounts/), [Tips & Tricks](https://actualbudget.org/docs/getting-started/tips-tricks) and some documentation for developers.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
|
|
5
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
6
|
+
|
|
7
|
+
const args = process.argv;
|
|
8
|
+
|
|
9
|
+
const options = {
|
|
10
|
+
help: {
|
|
11
|
+
type: 'boolean',
|
|
12
|
+
short: 'h',
|
|
13
|
+
},
|
|
14
|
+
version: {
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
short: 'v',
|
|
17
|
+
},
|
|
18
|
+
config: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const { values } = parseArgs({
|
|
24
|
+
args,
|
|
25
|
+
options,
|
|
26
|
+
allowPositionals: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (values.help) {
|
|
30
|
+
console.log(
|
|
31
|
+
[
|
|
32
|
+
'usage: actual-server [options]',
|
|
33
|
+
'',
|
|
34
|
+
'options:',
|
|
35
|
+
' --config Path to config file',
|
|
36
|
+
'',
|
|
37
|
+
' -h --help Print this list and exit.',
|
|
38
|
+
' -v --version Print the version and exit.',
|
|
39
|
+
'',
|
|
40
|
+
'Examples:',
|
|
41
|
+
'',
|
|
42
|
+
'Runs actual-server with default configuration',
|
|
43
|
+
' actual-server',
|
|
44
|
+
'',
|
|
45
|
+
'Runs actual-server with custom configuration',
|
|
46
|
+
' actual-server --config ./config.json',
|
|
47
|
+
].join('\n'),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
process.exit();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (values.version) {
|
|
54
|
+
console.log('v' + packageJson.version);
|
|
55
|
+
|
|
56
|
+
process.exit();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Read the config argument if specified
|
|
60
|
+
if (values.config) {
|
|
61
|
+
const configExists = existsSync(values.config);
|
|
62
|
+
|
|
63
|
+
if (!configExists) {
|
|
64
|
+
console.log(
|
|
65
|
+
`Please specify a valid config path. The path ${values.config} does not exist.`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
process.exit();
|
|
69
|
+
} else if (values.config) {
|
|
70
|
+
console.log(`Loading config from ${values.config}`);
|
|
71
|
+
process.env.ACTUAL_CONFIG_PATH = values.config;
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
// No config specified, use reasonable defaults
|
|
75
|
+
console.info(
|
|
76
|
+
'Using default config. You can specify a custom config with --config',
|
|
77
|
+
);
|
|
78
|
+
process.env.ACTUAL_DATA_DIR = './';
|
|
79
|
+
console.info(
|
|
80
|
+
'user-files and server-files will be created in the current directory',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// start the sync server
|
|
85
|
+
import('../app.js');
|
package/package.json
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actual-app/sync-server",
|
|
3
|
-
"version": "25.4.0-alpha.
|
|
3
|
+
"version": "25.4.0-alpha.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "actual syncing server",
|
|
6
|
-
"bin":
|
|
6
|
+
"bin": {
|
|
7
|
+
"actual-server": "./bin/actual-server.js"
|
|
8
|
+
},
|
|
7
9
|
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"app.js",
|
|
14
|
+
"migrations",
|
|
15
|
+
"default-db.sqlite",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
8
19
|
"scripts": {
|
|
9
20
|
"start": "node app",
|
|
10
21
|
"start-monitor": "nodemon app",
|
|
@@ -20,7 +31,7 @@
|
|
|
20
31
|
},
|
|
21
32
|
"dependencies": {
|
|
22
33
|
"@actual-app/crdt": "2.1.0",
|
|
23
|
-
"@actual-app/web": "25.4.0
|
|
34
|
+
"@actual-app/web": "25.4.0",
|
|
24
35
|
"bcrypt": "^5.1.1",
|
|
25
36
|
"better-sqlite3": "^11.9.1",
|
|
26
37
|
"body-parser": "^1.20.3",
|
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
# Integration new bank
|
|
2
|
-
|
|
3
|
-
If the default bank integration does not work for you, you can integrate a new bank by following these steps.
|
|
4
|
-
|
|
5
|
-
1. Find in [this google doc](https://docs.google.com/spreadsheets/d/1ogpzydzotOltbssrc3IQ8rhBLlIZbQgm5QCiiNJrkyA/edit#gid=489769432) what is the identifier of the bank which you want to integrate.
|
|
6
|
-
|
|
7
|
-
2. Launch frontend and backend server.
|
|
8
|
-
|
|
9
|
-
3. In the frontend, create a new linked account selecting the institution which you are interested in.
|
|
10
|
-
|
|
11
|
-
This will trigger the process of fetching the data from the bank and will log the data in the backend. Use this data to fill the logic of the bank class.
|
|
12
|
-
|
|
13
|
-
4. Create new a bank class based on an existing example in `app-gocardless/banks`.
|
|
14
|
-
|
|
15
|
-
Name of the file and class should follow the existing patterns and be created based on the ID of the integrated institution, found in step 1.
|
|
16
|
-
|
|
17
|
-
5. Fill the logic of `normalizeAccount`, `normalizeTransaction`, `sortTransactions`, and `calculateStartingBalance` functions.
|
|
18
|
-
You do not need to fill every function, only those which are necessary for the integration to work.
|
|
19
|
-
|
|
20
|
-
You should do it based on the data which you found in the logs.
|
|
21
|
-
|
|
22
|
-
Example logs which help you to fill:
|
|
23
|
-
|
|
24
|
-
- `normalizeAccount` function:
|
|
25
|
-
|
|
26
|
-
```log
|
|
27
|
-
Available account properties for new institution integration {
|
|
28
|
-
account: '{
|
|
29
|
-
"iban": "PL00000000000000000987654321",
|
|
30
|
-
"currency": "PLN",
|
|
31
|
-
"ownerName": "John Example",
|
|
32
|
-
"displayName": "Product name",
|
|
33
|
-
"product": "Daily account",
|
|
34
|
-
"usage": "PRIV",
|
|
35
|
-
"ownerAddressUnstructured": [
|
|
36
|
-
"POL",
|
|
37
|
-
"UL. Example 1",
|
|
38
|
-
"00-000 Warsaw"
|
|
39
|
-
],
|
|
40
|
-
"id": "XXXXXXXX-XXXX-XXXXX-XXXXXX-XXXXXXXXX",
|
|
41
|
-
"created": "2023-01-18T12:15:16.502446Z",
|
|
42
|
-
"last_accessed": null,
|
|
43
|
-
"institution_id": "MBANK_RETAIL_BREXPLPW",
|
|
44
|
-
"status": "READY",
|
|
45
|
-
"owner_name": "",
|
|
46
|
-
"institution": {
|
|
47
|
-
"id": "MBANK_RETAIL_BREXPLPW",
|
|
48
|
-
"name": "mBank Retail",
|
|
49
|
-
"bic": "BREXPLPW",
|
|
50
|
-
"transaction_total_days": "90",
|
|
51
|
-
"countries": [
|
|
52
|
-
"PL"
|
|
53
|
-
],
|
|
54
|
-
"logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png",
|
|
55
|
-
"supported_payments": {},
|
|
56
|
-
"supported_features": [
|
|
57
|
-
"access_scopes",
|
|
58
|
-
"business_accounts",
|
|
59
|
-
"card_accounts",
|
|
60
|
-
"corporate_accounts",
|
|
61
|
-
"pending_transactions",
|
|
62
|
-
"private_accounts"
|
|
63
|
-
]
|
|
64
|
-
}
|
|
65
|
-
}'
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
- `sortTransactions` function:
|
|
70
|
-
|
|
71
|
-
```log
|
|
72
|
-
Available (first 10) transactions properties for new integration of institution in sortTransactions function
|
|
73
|
-
{
|
|
74
|
-
top10SortedTransactions: '[
|
|
75
|
-
{
|
|
76
|
-
"transactionId": "20220101001",
|
|
77
|
-
"bookingDate": "2022-01-01",
|
|
78
|
-
"valueDate": "2022-01-01",
|
|
79
|
-
"transactionAmount": {
|
|
80
|
-
"amount": "5.01",
|
|
81
|
-
"currency": "EUR"
|
|
82
|
-
},
|
|
83
|
-
"creditorName": "JOHN EXAMPLE",
|
|
84
|
-
"creditorAccount": {
|
|
85
|
-
"iban": "PL00000000000000000987654321"
|
|
86
|
-
},
|
|
87
|
-
"debtorName": "CHRIS EXAMPLE",
|
|
88
|
-
"debtorAccount": {
|
|
89
|
-
"iban": "PL12345000000000000987654321"
|
|
90
|
-
},
|
|
91
|
-
"remittanceInformationUnstructured": "TEST BANK TRANSFER",
|
|
92
|
-
"remittanceInformationUnstructuredArray": [
|
|
93
|
-
"TEST BANK TRANSFER"
|
|
94
|
-
],
|
|
95
|
-
"balanceAfterTransaction": {
|
|
96
|
-
"balanceAmount": {
|
|
97
|
-
"amount": "448.52",
|
|
98
|
-
"currency": "EUR"
|
|
99
|
-
},
|
|
100
|
-
"balanceType": "interimBooked"
|
|
101
|
-
},
|
|
102
|
-
"internalTransactionId": "casfib7720c2a02c0331cw2"
|
|
103
|
-
}
|
|
104
|
-
]'
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
- `calculateStartingBalance` function:
|
|
109
|
-
|
|
110
|
-
```log
|
|
111
|
-
Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function {
|
|
112
|
-
balances: '[
|
|
113
|
-
{
|
|
114
|
-
"balanceAmount": {
|
|
115
|
-
"amount": "448.52",
|
|
116
|
-
"currency": "EUR"
|
|
117
|
-
},
|
|
118
|
-
"balanceType": "forwardAvailable"
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
"balanceAmount": {
|
|
122
|
-
"amount": "448.52",
|
|
123
|
-
"currency": "EUR"
|
|
124
|
-
},
|
|
125
|
-
"balanceType": "interimBooked"
|
|
126
|
-
}
|
|
127
|
-
]',
|
|
128
|
-
top10SortedTransactions: '[
|
|
129
|
-
{
|
|
130
|
-
"transactionId": "20220101001",
|
|
131
|
-
"bookingDate": "2022-01-01",
|
|
132
|
-
"valueDate": "2022-01-01",
|
|
133
|
-
"transactionAmount": {
|
|
134
|
-
"amount": "5.01",
|
|
135
|
-
"currency": "EUR"
|
|
136
|
-
},
|
|
137
|
-
"creditorName": "JOHN EXAMPLE",
|
|
138
|
-
"creditorAccount": {
|
|
139
|
-
"iban": "PL00000000000000000987654321"
|
|
140
|
-
},
|
|
141
|
-
"debtorName": "CHRIS EXAMPLE",
|
|
142
|
-
"debtorAccount": {
|
|
143
|
-
"iban": "PL12345000000000000987654321"
|
|
144
|
-
},
|
|
145
|
-
"remittanceInformationUnstructured": "TEST BANK TRANSFER",
|
|
146
|
-
"remittanceInformationUnstructuredArray": [
|
|
147
|
-
"TEST BANK TRANSFER"
|
|
148
|
-
],
|
|
149
|
-
"balanceAfterTransaction": {
|
|
150
|
-
"balanceAmount": {
|
|
151
|
-
"amount": "448.52",
|
|
152
|
-
"currency": "EUR"
|
|
153
|
-
},
|
|
154
|
-
"balanceType": "interimBooked"
|
|
155
|
-
},
|
|
156
|
-
"internalTransactionId": "casfib7720c2a02c0331cw2"
|
|
157
|
-
}
|
|
158
|
-
]'
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
6. Add new bank integration to `BankFactory` class in file `actual-server/app-gocardless/bank-factory.js`
|
|
163
|
-
|
|
164
|
-
7. Remember to add tests for new bank integration in
|
|
165
|
-
|
|
166
|
-
## normalizeTransaction
|
|
167
|
-
|
|
168
|
-
This is the most commonly used override as it allows you to change the data that is returned to the client.
|
|
169
|
-
|
|
170
|
-
Please follow the following patterns when implementing a custom normalizeTransaction method:
|
|
171
|
-
|
|
172
|
-
1. If you need to edit the values of transaction fields (excluding the transaction amount) do not mutate the original transaction object. Instead, create a shallow copy and make your changes there.
|
|
173
|
-
2. End the function by returning the result of calling the fallback normalizeTransaction method from integration-bank.js
|
|
174
|
-
|
|
175
|
-
E.g.
|
|
176
|
-
|
|
177
|
-
```js
|
|
178
|
-
import Fallback from './integration-bank.js';
|
|
179
|
-
|
|
180
|
-
export default {
|
|
181
|
-
...
|
|
182
|
-
|
|
183
|
-
normalizeTransaction(transaction, booked) {
|
|
184
|
-
// create a shallow copy of the transaction object
|
|
185
|
-
const editedTrans = { ...transaction };
|
|
186
|
-
|
|
187
|
-
// make any changes required to the copy
|
|
188
|
-
editedTrans.remittanceInformationUnstructured = transaction.remittanceInformationStructured;
|
|
189
|
-
|
|
190
|
-
// call the fallback method, passing in your edited transaction as the 3rd parameter
|
|
191
|
-
// this will calculate the date, payee name and notes fields based on your changes
|
|
192
|
-
// but leave the original fields available for mapping in the UI
|
|
193
|
-
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
...
|
|
197
|
-
}
|
|
198
|
-
```
|
|
1
|
+
# Integration new bank
|
|
2
|
+
|
|
3
|
+
If the default bank integration does not work for you, you can integrate a new bank by following these steps.
|
|
4
|
+
|
|
5
|
+
1. Find in [this google doc](https://docs.google.com/spreadsheets/d/1ogpzydzotOltbssrc3IQ8rhBLlIZbQgm5QCiiNJrkyA/edit#gid=489769432) what is the identifier of the bank which you want to integrate.
|
|
6
|
+
|
|
7
|
+
2. Launch frontend and backend server.
|
|
8
|
+
|
|
9
|
+
3. In the frontend, create a new linked account selecting the institution which you are interested in.
|
|
10
|
+
|
|
11
|
+
This will trigger the process of fetching the data from the bank and will log the data in the backend. Use this data to fill the logic of the bank class.
|
|
12
|
+
|
|
13
|
+
4. Create new a bank class based on an existing example in `app-gocardless/banks`.
|
|
14
|
+
|
|
15
|
+
Name of the file and class should follow the existing patterns and be created based on the ID of the integrated institution, found in step 1.
|
|
16
|
+
|
|
17
|
+
5. Fill the logic of `normalizeAccount`, `normalizeTransaction`, `sortTransactions`, and `calculateStartingBalance` functions.
|
|
18
|
+
You do not need to fill every function, only those which are necessary for the integration to work.
|
|
19
|
+
|
|
20
|
+
You should do it based on the data which you found in the logs.
|
|
21
|
+
|
|
22
|
+
Example logs which help you to fill:
|
|
23
|
+
|
|
24
|
+
- `normalizeAccount` function:
|
|
25
|
+
|
|
26
|
+
```log
|
|
27
|
+
Available account properties for new institution integration {
|
|
28
|
+
account: '{
|
|
29
|
+
"iban": "PL00000000000000000987654321",
|
|
30
|
+
"currency": "PLN",
|
|
31
|
+
"ownerName": "John Example",
|
|
32
|
+
"displayName": "Product name",
|
|
33
|
+
"product": "Daily account",
|
|
34
|
+
"usage": "PRIV",
|
|
35
|
+
"ownerAddressUnstructured": [
|
|
36
|
+
"POL",
|
|
37
|
+
"UL. Example 1",
|
|
38
|
+
"00-000 Warsaw"
|
|
39
|
+
],
|
|
40
|
+
"id": "XXXXXXXX-XXXX-XXXXX-XXXXXX-XXXXXXXXX",
|
|
41
|
+
"created": "2023-01-18T12:15:16.502446Z",
|
|
42
|
+
"last_accessed": null,
|
|
43
|
+
"institution_id": "MBANK_RETAIL_BREXPLPW",
|
|
44
|
+
"status": "READY",
|
|
45
|
+
"owner_name": "",
|
|
46
|
+
"institution": {
|
|
47
|
+
"id": "MBANK_RETAIL_BREXPLPW",
|
|
48
|
+
"name": "mBank Retail",
|
|
49
|
+
"bic": "BREXPLPW",
|
|
50
|
+
"transaction_total_days": "90",
|
|
51
|
+
"countries": [
|
|
52
|
+
"PL"
|
|
53
|
+
],
|
|
54
|
+
"logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png",
|
|
55
|
+
"supported_payments": {},
|
|
56
|
+
"supported_features": [
|
|
57
|
+
"access_scopes",
|
|
58
|
+
"business_accounts",
|
|
59
|
+
"card_accounts",
|
|
60
|
+
"corporate_accounts",
|
|
61
|
+
"pending_transactions",
|
|
62
|
+
"private_accounts"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}'
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `sortTransactions` function:
|
|
70
|
+
|
|
71
|
+
```log
|
|
72
|
+
Available (first 10) transactions properties for new integration of institution in sortTransactions function
|
|
73
|
+
{
|
|
74
|
+
top10SortedTransactions: '[
|
|
75
|
+
{
|
|
76
|
+
"transactionId": "20220101001",
|
|
77
|
+
"bookingDate": "2022-01-01",
|
|
78
|
+
"valueDate": "2022-01-01",
|
|
79
|
+
"transactionAmount": {
|
|
80
|
+
"amount": "5.01",
|
|
81
|
+
"currency": "EUR"
|
|
82
|
+
},
|
|
83
|
+
"creditorName": "JOHN EXAMPLE",
|
|
84
|
+
"creditorAccount": {
|
|
85
|
+
"iban": "PL00000000000000000987654321"
|
|
86
|
+
},
|
|
87
|
+
"debtorName": "CHRIS EXAMPLE",
|
|
88
|
+
"debtorAccount": {
|
|
89
|
+
"iban": "PL12345000000000000987654321"
|
|
90
|
+
},
|
|
91
|
+
"remittanceInformationUnstructured": "TEST BANK TRANSFER",
|
|
92
|
+
"remittanceInformationUnstructuredArray": [
|
|
93
|
+
"TEST BANK TRANSFER"
|
|
94
|
+
],
|
|
95
|
+
"balanceAfterTransaction": {
|
|
96
|
+
"balanceAmount": {
|
|
97
|
+
"amount": "448.52",
|
|
98
|
+
"currency": "EUR"
|
|
99
|
+
},
|
|
100
|
+
"balanceType": "interimBooked"
|
|
101
|
+
},
|
|
102
|
+
"internalTransactionId": "casfib7720c2a02c0331cw2"
|
|
103
|
+
}
|
|
104
|
+
]'
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `calculateStartingBalance` function:
|
|
109
|
+
|
|
110
|
+
```log
|
|
111
|
+
Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function {
|
|
112
|
+
balances: '[
|
|
113
|
+
{
|
|
114
|
+
"balanceAmount": {
|
|
115
|
+
"amount": "448.52",
|
|
116
|
+
"currency": "EUR"
|
|
117
|
+
},
|
|
118
|
+
"balanceType": "forwardAvailable"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"balanceAmount": {
|
|
122
|
+
"amount": "448.52",
|
|
123
|
+
"currency": "EUR"
|
|
124
|
+
},
|
|
125
|
+
"balanceType": "interimBooked"
|
|
126
|
+
}
|
|
127
|
+
]',
|
|
128
|
+
top10SortedTransactions: '[
|
|
129
|
+
{
|
|
130
|
+
"transactionId": "20220101001",
|
|
131
|
+
"bookingDate": "2022-01-01",
|
|
132
|
+
"valueDate": "2022-01-01",
|
|
133
|
+
"transactionAmount": {
|
|
134
|
+
"amount": "5.01",
|
|
135
|
+
"currency": "EUR"
|
|
136
|
+
},
|
|
137
|
+
"creditorName": "JOHN EXAMPLE",
|
|
138
|
+
"creditorAccount": {
|
|
139
|
+
"iban": "PL00000000000000000987654321"
|
|
140
|
+
},
|
|
141
|
+
"debtorName": "CHRIS EXAMPLE",
|
|
142
|
+
"debtorAccount": {
|
|
143
|
+
"iban": "PL12345000000000000987654321"
|
|
144
|
+
},
|
|
145
|
+
"remittanceInformationUnstructured": "TEST BANK TRANSFER",
|
|
146
|
+
"remittanceInformationUnstructuredArray": [
|
|
147
|
+
"TEST BANK TRANSFER"
|
|
148
|
+
],
|
|
149
|
+
"balanceAfterTransaction": {
|
|
150
|
+
"balanceAmount": {
|
|
151
|
+
"amount": "448.52",
|
|
152
|
+
"currency": "EUR"
|
|
153
|
+
},
|
|
154
|
+
"balanceType": "interimBooked"
|
|
155
|
+
},
|
|
156
|
+
"internalTransactionId": "casfib7720c2a02c0331cw2"
|
|
157
|
+
}
|
|
158
|
+
]'
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
6. Add new bank integration to `BankFactory` class in file `actual-server/app-gocardless/bank-factory.js`
|
|
163
|
+
|
|
164
|
+
7. Remember to add tests for new bank integration in
|
|
165
|
+
|
|
166
|
+
## normalizeTransaction
|
|
167
|
+
|
|
168
|
+
This is the most commonly used override as it allows you to change the data that is returned to the client.
|
|
169
|
+
|
|
170
|
+
Please follow the following patterns when implementing a custom normalizeTransaction method:
|
|
171
|
+
|
|
172
|
+
1. If you need to edit the values of transaction fields (excluding the transaction amount) do not mutate the original transaction object. Instead, create a shallow copy and make your changes there.
|
|
173
|
+
2. End the function by returning the result of calling the fallback normalizeTransaction method from integration-bank.js
|
|
174
|
+
|
|
175
|
+
E.g.
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
import Fallback from './integration-bank.js';
|
|
179
|
+
|
|
180
|
+
export default {
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
normalizeTransaction(transaction, booked) {
|
|
184
|
+
// create a shallow copy of the transaction object
|
|
185
|
+
const editedTrans = { ...transaction };
|
|
186
|
+
|
|
187
|
+
// make any changes required to the copy
|
|
188
|
+
editedTrans.remittanceInformationUnstructured = transaction.remittanceInformationStructured;
|
|
189
|
+
|
|
190
|
+
// call the fallback method, passing in your edited transaction as the 3rd parameter
|
|
191
|
+
// this will calculate the date, payee name and notes fields based on your changes
|
|
192
|
+
// but leave the original fields available for mapping in the UI
|
|
193
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
...
|
|
197
|
+
}
|
|
198
|
+
```
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<title>Actual</title>
|
|
6
|
-
</head>
|
|
7
|
-
<body>
|
|
8
|
-
<script>
|
|
9
|
-
window.close();
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<p>Please wait...</p>
|
|
13
|
-
<p>
|
|
14
|
-
The window should close automatically. If nothing happened you can close
|
|
15
|
-
this window or tab.
|
|
16
|
-
</p>
|
|
17
|
-
</body>
|
|
18
|
-
</html>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Actual</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<script>
|
|
9
|
+
window.close();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<p>Please wait...</p>
|
|
13
|
+
<p>
|
|
14
|
+
The window should close automatically. If nothing happened you can close
|
|
15
|
+
this window or tab.
|
|
16
|
+
</p>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
package/src/sql/messages.sql
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
CREATE TABLE messages_binary
|
|
3
|
-
(timestamp TEXT PRIMARY KEY,
|
|
4
|
-
is_encrypted BOOLEAN,
|
|
5
|
-
content bytea);
|
|
6
|
-
|
|
7
|
-
CREATE TABLE messages_merkles
|
|
8
|
-
(id INTEGER PRIMARY KEY,
|
|
9
|
-
merkle TEXT);
|
|
1
|
+
|
|
2
|
+
CREATE TABLE messages_binary
|
|
3
|
+
(timestamp TEXT PRIMARY KEY,
|
|
4
|
+
is_encrypted BOOLEAN,
|
|
5
|
+
content bytea);
|
|
6
|
+
|
|
7
|
+
CREATE TABLE messages_merkles
|
|
8
|
+
(id INTEGER PRIMARY KEY,
|
|
9
|
+
merkle TEXT);
|
package/.dockerignore
DELETED
package/babel.config.json
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
'use strict';
|
|
4
|
-
import packageJson from '../../package.json' with { type: 'json' };
|
|
5
|
-
|
|
6
|
-
const getArgs = () =>
|
|
7
|
-
process.argv.reduce((args, arg) => {
|
|
8
|
-
// long arg
|
|
9
|
-
if (arg.slice(0, 2) === '--') {
|
|
10
|
-
const longArg = arg.split('=');
|
|
11
|
-
const longArgFlag = longArg[0].slice(2);
|
|
12
|
-
const longArgValue = longArg.length > 1 ? longArg[1] : true;
|
|
13
|
-
args[longArgFlag] = longArgValue;
|
|
14
|
-
}
|
|
15
|
-
// flags
|
|
16
|
-
else if (arg[0] === '-') {
|
|
17
|
-
const flags = arg.slice(1).split('');
|
|
18
|
-
flags.forEach(flag => {
|
|
19
|
-
args[flag] = true;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
return args;
|
|
23
|
-
}, {});
|
|
24
|
-
|
|
25
|
-
const args = getArgs();
|
|
26
|
-
|
|
27
|
-
if (args.h || args.help) {
|
|
28
|
-
console.log(
|
|
29
|
-
[
|
|
30
|
-
'usage: @actual-app/sync-server [options]',
|
|
31
|
-
'',
|
|
32
|
-
'options:',
|
|
33
|
-
' --config Path to config file',
|
|
34
|
-
'',
|
|
35
|
-
' -h --help Print this list and exit.',
|
|
36
|
-
' -v --version Print the version and exit.',
|
|
37
|
-
].join('\n'),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
process.exit();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (args.v || args.version) {
|
|
44
|
-
console.log('v' + packageJson.version);
|
|
45
|
-
|
|
46
|
-
process.exit();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// start the sync server
|
|
50
|
-
if (args.config) {
|
|
51
|
-
console.log(`Loading config from ${args.config}`);
|
|
52
|
-
process.env.ACTUAL_CONFIG_PATH = args.config;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
import('../../app.js');
|
package/docker/alpine.Dockerfile
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
FROM alpine:3.18 AS deps
|
|
2
|
-
|
|
3
|
-
# Install required packages
|
|
4
|
-
RUN apk add --no-cache nodejs yarn python3 openssl build-base
|
|
5
|
-
|
|
6
|
-
WORKDIR /app
|
|
7
|
-
|
|
8
|
-
# Copy only the files needed for installing dependencies
|
|
9
|
-
COPY .yarn ./.yarn
|
|
10
|
-
COPY yarn.lock package.json .yarnrc.yml ./
|
|
11
|
-
COPY packages/api/package.json packages/api/package.json
|
|
12
|
-
COPY packages/component-library/package.json packages/component-library/package.json
|
|
13
|
-
COPY packages/crdt/package.json packages/crdt/package.json
|
|
14
|
-
COPY packages/desktop-client/package.json packages/desktop-client/package.json
|
|
15
|
-
COPY packages/desktop-electron/package.json packages/desktop-electron/package.json
|
|
16
|
-
COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
|
|
17
|
-
COPY packages/loot-core/package.json packages/loot-core/package.json
|
|
18
|
-
COPY packages/sync-server/package.json packages/sync-server/package.json
|
|
19
|
-
|
|
20
|
-
# Avoiding memory issues with ARMv7
|
|
21
|
-
RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi
|
|
22
|
-
|
|
23
|
-
# Focus the workspaces in production mode
|
|
24
|
-
RUN if [ "$(uname -m)" = "armv7l" ]; then npm_config_build_from_source=true yarn workspaces focus @actual-app/sync-server --production; else yarn workspaces focus @actual-app/sync-server --production; fi
|
|
25
|
-
|
|
26
|
-
FROM deps AS builder
|
|
27
|
-
|
|
28
|
-
WORKDIR /app
|
|
29
|
-
|
|
30
|
-
COPY packages/sync-server ./packages/sync-server
|
|
31
|
-
|
|
32
|
-
# Remove symbolic links for @actual-app/web and @actual-app/sync-server
|
|
33
|
-
RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server
|
|
34
|
-
|
|
35
|
-
# Copy in the @actual-app/web artifacts manually, so we don't need the entire packages folder
|
|
36
|
-
COPY packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json
|
|
37
|
-
COPY packages/desktop-client/build ./node_modules/@actual-app/web/build
|
|
38
|
-
|
|
39
|
-
FROM alpine:3.18 AS prod
|
|
40
|
-
|
|
41
|
-
# Minimal runtime dependencies
|
|
42
|
-
RUN apk add --no-cache nodejs tini
|
|
43
|
-
|
|
44
|
-
# Create a non-root user
|
|
45
|
-
ARG USERNAME=actual
|
|
46
|
-
ARG USER_UID=1001
|
|
47
|
-
ARG USER_GID=$USER_UID
|
|
48
|
-
RUN addgroup -S ${USERNAME} -g ${USER_GID} && adduser -S ${USERNAME} -G ${USERNAME} -u ${USER_UID}
|
|
49
|
-
RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data
|
|
50
|
-
|
|
51
|
-
WORKDIR /app
|
|
52
|
-
ENV NODE_ENV=production
|
|
53
|
-
|
|
54
|
-
# Pull in only the necessary artifacts (built node_modules, server files, etc.)
|
|
55
|
-
COPY --from=builder /app/node_modules /app/node_modules
|
|
56
|
-
COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./
|
|
57
|
-
COPY --from=builder /app/packages/sync-server/src ./src
|
|
58
|
-
COPY --from=builder /app/packages/sync-server/migrations ./migrations
|
|
59
|
-
|
|
60
|
-
ENTRYPOINT ["/sbin/tini","-g", "--"]
|
|
61
|
-
EXPOSE 5006
|
|
62
|
-
CMD ["node", "app.js"]
|
package/docker/ubuntu.Dockerfile
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
FROM node:18-bookworm AS deps
|
|
2
|
-
|
|
3
|
-
# Install required packages
|
|
4
|
-
RUN apt-get update && apt-get install -y openssl
|
|
5
|
-
|
|
6
|
-
WORKDIR /app
|
|
7
|
-
|
|
8
|
-
# Copy only the files needed for installing dependencies
|
|
9
|
-
COPY .yarn ./.yarn
|
|
10
|
-
COPY yarn.lock package.json .yarnrc.yml ./
|
|
11
|
-
COPY packages/api/package.json packages/api/package.json
|
|
12
|
-
COPY packages/component-library/package.json packages/component-library/package.json
|
|
13
|
-
COPY packages/crdt/package.json packages/crdt/package.json
|
|
14
|
-
COPY packages/desktop-client/package.json packages/desktop-client/package.json
|
|
15
|
-
COPY packages/desktop-electron/package.json packages/desktop-electron/package.json
|
|
16
|
-
COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
|
|
17
|
-
COPY packages/loot-core/package.json packages/loot-core/package.json
|
|
18
|
-
COPY packages/sync-server/package.json packages/sync-server/package.json
|
|
19
|
-
|
|
20
|
-
# Avoiding memory issues with ARMv7
|
|
21
|
-
RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi
|
|
22
|
-
|
|
23
|
-
# Focus the workspaces in production mode
|
|
24
|
-
RUN yarn workspaces focus @actual-app/sync-server --production
|
|
25
|
-
|
|
26
|
-
FROM deps AS builder
|
|
27
|
-
|
|
28
|
-
WORKDIR /app
|
|
29
|
-
|
|
30
|
-
COPY packages/sync-server ./packages/sync-server
|
|
31
|
-
|
|
32
|
-
# Remove symbolic links for @actual-app/web and @actual-app/sync-server
|
|
33
|
-
RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server
|
|
34
|
-
|
|
35
|
-
# Copy in the @actual-app/web artifacts manually, so we don't need the entire packages folder
|
|
36
|
-
COPY packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json
|
|
37
|
-
COPY packages/desktop-client/build ./node_modules/@actual-app/web/build
|
|
38
|
-
|
|
39
|
-
FROM node:18-bookworm-slim AS prod
|
|
40
|
-
|
|
41
|
-
# Minimal runtime dependencies
|
|
42
|
-
RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/*
|
|
43
|
-
|
|
44
|
-
# Create a non-root user
|
|
45
|
-
ARG USERNAME=actual
|
|
46
|
-
ARG USER_UID=1001
|
|
47
|
-
ARG USER_GID=$USER_UID
|
|
48
|
-
RUN groupadd --gid $USER_GID $USERNAME \
|
|
49
|
-
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
|
|
50
|
-
&& mkdir /data && chown -R ${USERNAME}:${USERNAME} /data
|
|
51
|
-
|
|
52
|
-
WORKDIR /app
|
|
53
|
-
ENV NODE_ENV=production
|
|
54
|
-
|
|
55
|
-
# Pull in only the necessary artifacts (built node_modules, server files, etc.)
|
|
56
|
-
COPY --from=builder /app/node_modules /app/node_modules
|
|
57
|
-
COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./
|
|
58
|
-
COPY --from=builder /app/packages/sync-server/src ./src
|
|
59
|
-
COPY --from=builder /app/packages/sync-server/migrations ./migrations
|
|
60
|
-
|
|
61
|
-
ENTRYPOINT ["/usr/bin/tini","-g", "--"]
|
|
62
|
-
EXPOSE 5006
|
|
63
|
-
CMD ["node", "app.js"]
|
package/docker-compose.yml
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
services:
|
|
2
|
-
actual_server:
|
|
3
|
-
image: docker.io/actualbudget/actual-server:latest
|
|
4
|
-
ports:
|
|
5
|
-
# This line makes Actual available at port 5006 of the device you run the server on,
|
|
6
|
-
# i.e. http://localhost:5006. You can change the first number to change the port, if you want.
|
|
7
|
-
- '5006:5006'
|
|
8
|
-
environment:
|
|
9
|
-
# Uncomment any of the lines below to set configuration options.
|
|
10
|
-
# - ACTUAL_HTTPS_KEY=/data/selfhost.key
|
|
11
|
-
# - ACTUAL_HTTPS_CERT=/data/selfhost.crt
|
|
12
|
-
# - ACTUAL_PORT=5006
|
|
13
|
-
# - ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20
|
|
14
|
-
# - ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50
|
|
15
|
-
# - ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20
|
|
16
|
-
# See all options and more details at https://actualbudget.github.io/docs/Installing/Configuration
|
|
17
|
-
# !! If you are not using any of these options, remove the 'environment:' tag entirely.
|
|
18
|
-
volumes:
|
|
19
|
-
# Change './actual-data' below to the path to the folder you want Actual to store its data in on your server.
|
|
20
|
-
# '/data' is the path Actual will look for its files in by default, so leave that as-is.
|
|
21
|
-
- ./actual-data:/data
|
|
22
|
-
healthcheck:
|
|
23
|
-
# Enable health check for the instance
|
|
24
|
-
test: ['CMD-SHELL', 'node src/scripts/health-check.js']
|
|
25
|
-
interval: 60s
|
|
26
|
-
timeout: 10s
|
|
27
|
-
retries: 3
|
|
28
|
-
start_period: 20s
|
|
29
|
-
restart: unless-stopped
|
package/jest.config.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"globalSetup": "./jest.global-setup.js",
|
|
3
|
-
"globalTeardown": "./jest.global-teardown.js",
|
|
4
|
-
"testPathIgnorePatterns": ["dist", "/node_modules/", "/build/"],
|
|
5
|
-
"roots": ["<rootDir>"],
|
|
6
|
-
"moduleFileExtensions": ["ts", "js", "json"],
|
|
7
|
-
"testEnvironment": "node",
|
|
8
|
-
"collectCoverage": true,
|
|
9
|
-
"collectCoverageFrom": ["**/*.{js,ts,tsx}"],
|
|
10
|
-
"coveragePathIgnorePatterns": [
|
|
11
|
-
"dist",
|
|
12
|
-
"/node_modules/",
|
|
13
|
-
"/build/",
|
|
14
|
-
"/coverage/"
|
|
15
|
-
],
|
|
16
|
-
"coverageReporters": ["html", "lcov", "text", "text-summary"],
|
|
17
|
-
"resetMocks": true,
|
|
18
|
-
"restoreMocks": true
|
|
19
|
-
}
|
package/jest.global-setup.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { getAccountDb } from './src/account-db.js';
|
|
2
|
-
import { run as runMigrations } from './src/migrations.js';
|
|
3
|
-
|
|
4
|
-
const GENERIC_ADMIN_ID = 'genericAdmin';
|
|
5
|
-
const GENERIC_USER_ID = 'genericUser';
|
|
6
|
-
const ADMIN_ROLE_ID = 'ADMIN';
|
|
7
|
-
const BASIC_ROLE_ID = 'BASIC';
|
|
8
|
-
|
|
9
|
-
const createUser = (userId, userName, role, owner = 0, enabled = 1) => {
|
|
10
|
-
const missingParams = [];
|
|
11
|
-
if (!userId) missingParams.push('userId');
|
|
12
|
-
if (!userName) missingParams.push('userName');
|
|
13
|
-
if (!role) missingParams.push('role');
|
|
14
|
-
if (missingParams.length > 0) {
|
|
15
|
-
throw new Error(`Missing required parameters: ${missingParams.join(', ')}`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
typeof userId !== 'string' ||
|
|
20
|
-
typeof userName !== 'string' ||
|
|
21
|
-
typeof role !== 'string'
|
|
22
|
-
) {
|
|
23
|
-
throw new Error(
|
|
24
|
-
'Invalid parameter types. userId, userName, and role must be strings',
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
getAccountDb().mutate(
|
|
30
|
-
'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, ?, ?, ?)',
|
|
31
|
-
[userId, userName, userName, enabled, owner, role],
|
|
32
|
-
);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error(`Error creating user ${userName}:`, error);
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const setSessionUser = (userId, token = 'valid-token') => {
|
|
40
|
-
if (!userId) {
|
|
41
|
-
throw new Error('userId is required');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const db = getAccountDb();
|
|
46
|
-
const session = db.first('SELECT token FROM sessions WHERE token = ?', [
|
|
47
|
-
token,
|
|
48
|
-
]);
|
|
49
|
-
if (!session) {
|
|
50
|
-
throw new Error(`Session not found for token: ${token}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
db.mutate('UPDATE sessions SET user_id = ? WHERE token = ?', [
|
|
54
|
-
userId,
|
|
55
|
-
token,
|
|
56
|
-
]);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error(`Error updating session for user ${userId}:`, error);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// eslint-disable-next-line import/no-default-export
|
|
64
|
-
export default async function setup() {
|
|
65
|
-
const NEVER_EXPIRES = -1; // or consider using a far future timestamp
|
|
66
|
-
|
|
67
|
-
await runMigrations();
|
|
68
|
-
|
|
69
|
-
createUser(GENERIC_ADMIN_ID, 'admin', ADMIN_ROLE_ID, 1);
|
|
70
|
-
|
|
71
|
-
// Insert a fake "valid-token" fixture that can be reused
|
|
72
|
-
const db = getAccountDb();
|
|
73
|
-
try {
|
|
74
|
-
await db.mutate('BEGIN TRANSACTION');
|
|
75
|
-
|
|
76
|
-
await db.mutate('DELETE FROM sessions');
|
|
77
|
-
await db.mutate(
|
|
78
|
-
'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)',
|
|
79
|
-
['valid-token', NEVER_EXPIRES, 'genericAdmin'],
|
|
80
|
-
);
|
|
81
|
-
await db.mutate(
|
|
82
|
-
'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)',
|
|
83
|
-
['valid-token-admin', NEVER_EXPIRES, 'genericAdmin'],
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
await db.mutate(
|
|
87
|
-
'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)',
|
|
88
|
-
['valid-token-user', NEVER_EXPIRES, 'genericUser'],
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
await db.mutate('COMMIT');
|
|
92
|
-
} catch (error) {
|
|
93
|
-
await db.mutate('ROLLBACK');
|
|
94
|
-
throw new Error(`Failed to setup test sessions: ${error.message}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
setSessionUser('genericAdmin');
|
|
98
|
-
setSessionUser('genericAdmin', 'valid-token-admin');
|
|
99
|
-
|
|
100
|
-
createUser(GENERIC_USER_ID, 'user', BASIC_ROLE_ID, 1);
|
|
101
|
-
}
|
package/jest.global-teardown.js
DELETED
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
// DOM for URL global in Node 16+
|
|
5
|
-
"lib": ["ES2021"],
|
|
6
|
-
"allowSyntheticDefaultImports": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"experimentalDecorators": true,
|
|
9
|
-
"resolveJsonModule": true,
|
|
10
|
-
"downlevelIteration": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"jsx": "preserve",
|
|
13
|
-
// Check JS files too
|
|
14
|
-
"allowJs": true,
|
|
15
|
-
"moduleResolution": "node16",
|
|
16
|
-
"module": "node16",
|
|
17
|
-
"outDir": "build"
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*.js", "types/global.d.ts"],
|
|
20
|
-
"exclude": ["node_modules", "build", "./app-plaid.js", "coverage"]
|
|
21
|
-
}
|