@actual-app/sync-server 25.5.0-alpha.2 → 25.5.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.
- package/README.md +6 -7
- package/bin/actual-server.js +46 -14
- package/package.json +7 -7
- package/src/accounts/openid.js +11 -4
- package/src/app-account.js +3 -3
- package/src/app-gocardless/README.md +198 -198
- package/src/app-gocardless/banks/commerzbank_cobadeff.js +4 -1
- package/src/app-gocardless/banks/seb_kort_bank_ab.js +1 -0
- package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +23 -0
- package/src/app-gocardless/banks/tests/integration_bank.spec.js +1 -3
- package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +2 -3
- package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +1 -3
- package/src/app-gocardless/banks/util/escape-regexp.js +4 -0
- package/src/app-gocardless/link.html +18 -18
- package/src/app-gocardless/services/tests/gocardless-service.spec.js +58 -58
- package/src/app-simplefin/app-simplefin.js +37 -50
- package/src/app-sync/tests/services/files-service.test.js +3 -6
- package/src/app.js +6 -2
- package/src/sql/messages.sql +9 -9
package/README.md
CHANGED
|
@@ -28,12 +28,11 @@ actual-server [options]
|
|
|
28
28
|
|
|
29
29
|
**Available options**
|
|
30
30
|
|
|
31
|
-
| Command
|
|
32
|
-
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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. |
|
|
37
36
|
|
|
38
37
|
**Examples**
|
|
39
38
|
|
|
@@ -43,7 +42,7 @@ Run with default configuration
|
|
|
43
42
|
actual-server
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
Run with custom configuration
|
|
47
46
|
|
|
48
47
|
```bash
|
|
49
48
|
actual-server --config ./config.json
|
package/bin/actual-server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { parseArgs } from 'node:util';
|
|
4
6
|
|
|
5
|
-
import packageJson from '../package.json' with { type: 'json' };
|
|
6
|
-
|
|
7
7
|
const args = process.argv;
|
|
8
8
|
|
|
9
9
|
const options = {
|
|
@@ -51,12 +51,38 @@ if (values.help) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
if (values.version) {
|
|
54
|
-
|
|
54
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
const packageJsonPath = resolve(__dirname, '../package.json');
|
|
56
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
55
57
|
|
|
58
|
+
console.log('v' + packageJson.version);
|
|
56
59
|
process.exit();
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
const setupDataDir = (dataDir = undefined) => {
|
|
63
|
+
if (process.env.ACTUAL_DATA_DIR) {
|
|
64
|
+
return; // Env variables must not be overwritten
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (dataDir) {
|
|
68
|
+
process.env.ACTUAL_DATA_DIR = dataDir; // Use the dir specified
|
|
69
|
+
} else {
|
|
70
|
+
// Setup defaults
|
|
71
|
+
if (existsSync('./data')) {
|
|
72
|
+
// The default data directory exists - use it
|
|
73
|
+
console.info('Found existing data directory');
|
|
74
|
+
process.env.ACTUAL_DATA_DIR = './data';
|
|
75
|
+
} else {
|
|
76
|
+
console.info(
|
|
77
|
+
'Using default data directory. You can specify a custom config with --config',
|
|
78
|
+
);
|
|
79
|
+
process.env.ACTUAL_DATA_DIR = './';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.info(`Data directory: ${process.env.ACTUAL_DATA_DIR}`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
60
86
|
if (values.config) {
|
|
61
87
|
const configExists = existsSync(values.config);
|
|
62
88
|
|
|
@@ -66,19 +92,25 @@ if (values.config) {
|
|
|
66
92
|
);
|
|
67
93
|
|
|
68
94
|
process.exit();
|
|
69
|
-
} else
|
|
95
|
+
} else {
|
|
70
96
|
console.log(`Loading config from ${values.config}`);
|
|
97
|
+
const configJson = JSON.parse(readFileSync(values.config, 'utf-8'));
|
|
71
98
|
process.env.ACTUAL_CONFIG_PATH = values.config;
|
|
99
|
+
setupDataDir(configJson.dataDir);
|
|
72
100
|
}
|
|
73
101
|
} else {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
// If no config is specified, check for a default config in the current directory
|
|
103
|
+
const defaultConfigJsonFile = './config.json';
|
|
104
|
+
const configExists = existsSync(defaultConfigJsonFile);
|
|
105
|
+
|
|
106
|
+
if (configExists) {
|
|
107
|
+
console.info('Found config.json in the current directory');
|
|
108
|
+
const configJson = JSON.parse(readFileSync(defaultConfigJsonFile, 'utf-8'));
|
|
109
|
+
process.env.ACTUAL_CONFIG_PATH = defaultConfigJsonFile;
|
|
110
|
+
setupDataDir(configJson.dataDir);
|
|
111
|
+
} else {
|
|
112
|
+
setupDataDir(); // No default config exists - setup data dir with defaults
|
|
113
|
+
}
|
|
82
114
|
}
|
|
83
115
|
|
|
84
116
|
// start the sync server
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actual-app/sync-server",
|
|
3
|
-
"version": "25.5.0
|
|
3
|
+
"version": "25.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "actual syncing server",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"start": "node app",
|
|
21
21
|
"start-monitor": "nodemon app",
|
|
22
22
|
"build": "tsc",
|
|
23
|
-
"test": "NODE_ENV=test NODE_OPTIONS='--experimental-vm-modules --trace-warnings'
|
|
23
|
+
"test": "NODE_ENV=test NODE_OPTIONS='--experimental-vm-modules --trace-warnings' vitest",
|
|
24
24
|
"db:migrate": "NODE_ENV=development node src/run-migrations.js up",
|
|
25
25
|
"db:downgrade": "NODE_ENV=development node src/run-migrations.js down",
|
|
26
26
|
"db:test-migrate": "NODE_ENV=test node src/run-migrations.js up",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@actual-app/crdt": "2.1.0",
|
|
34
|
-
"@actual-app/web": "25.
|
|
34
|
+
"@actual-app/web": "25.5.0",
|
|
35
35
|
"bcrypt": "^5.1.1",
|
|
36
36
|
"better-sqlite3": "^11.9.1",
|
|
37
37
|
"body-parser": "^1.20.3",
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
"@types/cors": "^2.8.17",
|
|
63
63
|
"@types/express": "^5.0.0",
|
|
64
64
|
"@types/express-actuator": "^1.8.3",
|
|
65
|
-
"@types/jest": "^29.5.14",
|
|
66
65
|
"@types/node": "^22.14.0",
|
|
67
66
|
"@types/supertest": "^2.0.16",
|
|
68
67
|
"@types/uuid": "^9.0.8",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
68
|
+
"@vitest/coverage-v8": "3.1.1",
|
|
69
|
+
"http-proxy-middleware": "^3.0.5",
|
|
71
70
|
"nodemon": "^3.1.9",
|
|
72
71
|
"supertest": "^6.3.4",
|
|
73
|
-
"typescript": "^5.8.2"
|
|
72
|
+
"typescript": "^5.8.2",
|
|
73
|
+
"vitest": "^3.0.2"
|
|
74
74
|
}
|
|
75
75
|
}
|
package/src/accounts/openid.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { generators, Issuer } from 'openid-client';
|
|
2
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
clearExpiredSessions,
|
|
6
|
+
getAccountDb,
|
|
7
|
+
listLoginMethods,
|
|
8
|
+
} from '../account-db.js';
|
|
5
9
|
import { config } from '../load-config.js';
|
|
6
10
|
import {
|
|
7
11
|
getUserByUsername,
|
|
@@ -99,10 +103,13 @@ export async function loginWithOpenIdSetup(
|
|
|
99
103
|
[''],
|
|
100
104
|
);
|
|
101
105
|
if (countUsersWithUserName === 0) {
|
|
102
|
-
const
|
|
106
|
+
const methods = listLoginMethods();
|
|
107
|
+
if (methods.some(authMethod => authMethod.method === 'password')) {
|
|
108
|
+
const valid = checkPassword(firstTimeLoginPassword);
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
if (!valid) {
|
|
111
|
+
return { error: 'invalid-password' };
|
|
112
|
+
}
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
|
package/src/app-account.js
CHANGED
|
@@ -84,7 +84,7 @@ app.post('/login', async (req, res) => {
|
|
|
84
84
|
break;
|
|
85
85
|
}
|
|
86
86
|
case 'openid': {
|
|
87
|
-
if (!isValidRedirectUrl(req.body.
|
|
87
|
+
if (!isValidRedirectUrl(req.body.returnUrl)) {
|
|
88
88
|
res
|
|
89
89
|
.status(400)
|
|
90
90
|
.send({ status: 'error', reason: 'Invalid redirect URL' });
|
|
@@ -92,14 +92,14 @@ app.post('/login', async (req, res) => {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const { error, url } = await loginWithOpenIdSetup(
|
|
95
|
-
req.body.
|
|
95
|
+
req.body.returnUrl,
|
|
96
96
|
req.body.password,
|
|
97
97
|
);
|
|
98
98
|
if (error) {
|
|
99
99
|
res.status(400).send({ status: 'error', reason: error });
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
res.send({ status: 'ok', data: {
|
|
102
|
+
res.send({ status: 'ok', data: { returnUrl: url } });
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -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,4 +1,5 @@
|
|
|
1
1
|
import Fallback from './integration-bank.js';
|
|
2
|
+
import { escapeRegExp } from './util/escape-regexp.js';
|
|
2
3
|
|
|
3
4
|
/** @type {import('./bank.interface.js').IBank} */
|
|
4
5
|
export default {
|
|
@@ -38,7 +39,9 @@ export default {
|
|
|
38
39
|
// Clean up remittanceInformation, deduplicate payee (removing slashes ...
|
|
39
40
|
// ... that are added to the remittanceInformation field), and ...
|
|
40
41
|
// ... remove clutter like "End-to-End-Ref.: NOTPROVIDED"
|
|
41
|
-
const payee =
|
|
42
|
+
const payee = escapeRegExp(
|
|
43
|
+
transaction.creditorName || transaction.debtorName || '',
|
|
44
|
+
);
|
|
42
45
|
editedTrans.remittanceInformationUnstructured =
|
|
43
46
|
editedTrans.remittanceInformationUnstructured
|
|
44
47
|
.replace(/\s*(,)?\s+/g, '$1 ')
|
|
@@ -106,5 +106,28 @@ describe('CommerzbankCobadeff', () => {
|
|
|
106
106
|
'CREDITOR00BIC CREDITOR000IBAN DESCRIPTION, Dauerauftrag',
|
|
107
107
|
);
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it('correctly uses regex on payee with special characters', () => {
|
|
111
|
+
const transaction = {
|
|
112
|
+
endToEndId: '1234567890',
|
|
113
|
+
mandateId: '1234567890',
|
|
114
|
+
bookingDate: '2025-04-18',
|
|
115
|
+
valueDate: '2025-04-18',
|
|
116
|
+
transactionAmount: {
|
|
117
|
+
amount: '-1',
|
|
118
|
+
currency: 'EUR',
|
|
119
|
+
},
|
|
120
|
+
creditorName: 'Netto Marken-Discount Halle (Saale',
|
|
121
|
+
remittanceInformationUnstructured: 'Example',
|
|
122
|
+
remittanceInformationUnstructuredArray: ['Example'],
|
|
123
|
+
remittanceInformationStructured: 'Example',
|
|
124
|
+
// internalTransactionId: '3815213adb654baeadfb231c853',
|
|
125
|
+
};
|
|
126
|
+
const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction(
|
|
127
|
+
transaction,
|
|
128
|
+
false,
|
|
129
|
+
);
|
|
130
|
+
expect(normalizedTransaction.notes).toEqual('Example');
|
|
131
|
+
});
|
|
109
132
|
});
|
|
110
133
|
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
mockExtendAccountsAboutInstitutions,
|
|
5
3
|
mockInstitution,
|
|
@@ -10,7 +8,7 @@ describe('IntegrationBank', () => {
|
|
|
10
8
|
let consoleSpy;
|
|
11
9
|
|
|
12
10
|
beforeEach(() => {
|
|
13
|
-
consoleSpy =
|
|
11
|
+
consoleSpy = vi.spyOn(console, 'debug');
|
|
14
12
|
});
|
|
15
13
|
|
|
16
14
|
describe('normalizeAccount', () => {
|
|
@@ -201,9 +201,8 @@ describe('SpkMarburgBiedenkopfHeladef1mar', () => {
|
|
|
201
201
|
normalizeTransactions[a] = normalizeTransactions[b];
|
|
202
202
|
normalizeTransactions[b] = swap;
|
|
203
203
|
};
|
|
204
|
-
swap(1,
|
|
205
|
-
swap(
|
|
206
|
-
swap(0, 7);
|
|
204
|
+
swap(1, 3);
|
|
205
|
+
swap(0, 2);
|
|
207
206
|
const sortedTransactions =
|
|
208
207
|
SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions);
|
|
209
208
|
expect(sortedTransactions).toEqual(originalOrder);
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
|
|
3
1
|
import SskDusseldorfDussdeddxxx from '../ssk_dusseldorf_dussdeddxxx.js';
|
|
4
2
|
|
|
5
3
|
describe('ssk_dusseldorf_dussdeddxxx', () => {
|
|
6
4
|
let consoleSpy;
|
|
7
5
|
|
|
8
6
|
beforeEach(() => {
|
|
9
|
-
consoleSpy =
|
|
7
|
+
consoleSpy = vi.spyOn(console, 'debug');
|
|
10
8
|
});
|
|
11
9
|
|
|
12
10
|
afterEach(() => {
|
|
@@ -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>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
AccessDeniedError,
|
|
5
3
|
AccountNotLinkedToRequisition,
|
|
@@ -51,29 +49,29 @@ describe('goCardlessService', () => {
|
|
|
51
49
|
let setTokenSpy;
|
|
52
50
|
|
|
53
51
|
beforeEach(() => {
|
|
54
|
-
getInstitutionsSpy =
|
|
55
|
-
getInstitutionSpy =
|
|
56
|
-
getRequisitionsSpy =
|
|
57
|
-
deleteRequisitionsSpy =
|
|
58
|
-
createRequisitionSpy =
|
|
59
|
-
getBalancesSpy =
|
|
60
|
-
getTransactionsSpy =
|
|
61
|
-
getDetailsSpy =
|
|
62
|
-
getMetadataSpy =
|
|
63
|
-
setTokenSpy =
|
|
52
|
+
getInstitutionsSpy = vi.spyOn(client, 'getInstitutions');
|
|
53
|
+
getInstitutionSpy = vi.spyOn(client, 'getInstitutionById');
|
|
54
|
+
getRequisitionsSpy = vi.spyOn(client, 'getRequisitionById');
|
|
55
|
+
deleteRequisitionsSpy = vi.spyOn(client, 'deleteRequisition');
|
|
56
|
+
createRequisitionSpy = vi.spyOn(client, 'initSession');
|
|
57
|
+
getBalancesSpy = vi.spyOn(client, 'getBalances');
|
|
58
|
+
getTransactionsSpy = vi.spyOn(client, 'getTransactions');
|
|
59
|
+
getDetailsSpy = vi.spyOn(client, 'getDetails');
|
|
60
|
+
getMetadataSpy = vi.spyOn(client, 'getMetadata');
|
|
61
|
+
setTokenSpy = vi.spyOn(goCardlessService, 'setToken');
|
|
64
62
|
});
|
|
65
63
|
|
|
66
64
|
afterEach(() => {
|
|
67
|
-
|
|
65
|
+
vi.resetAllMocks();
|
|
68
66
|
});
|
|
69
67
|
|
|
70
68
|
describe('#getLinkedRequisition', () => {
|
|
71
69
|
it('returns requisition', async () => {
|
|
72
70
|
setTokenSpy.mockResolvedValue();
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
vi.spyOn(goCardlessService, 'getRequisition').mockResolvedValue(
|
|
73
|
+
mockRequisition,
|
|
74
|
+
);
|
|
77
75
|
|
|
78
76
|
expect(
|
|
79
77
|
await goCardlessService.getLinkedRequisition(requisitionId),
|
|
@@ -83,9 +81,10 @@ describe('goCardlessService', () => {
|
|
|
83
81
|
it('throws RequisitionNotLinked error if requisition status is different than LN', async () => {
|
|
84
82
|
setTokenSpy.mockResolvedValue();
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
vi.spyOn(goCardlessService, 'getRequisition').mockResolvedValue({
|
|
85
|
+
...mockRequisition,
|
|
86
|
+
status: 'ER',
|
|
87
|
+
});
|
|
89
88
|
|
|
90
89
|
await expect(() =>
|
|
91
90
|
goCardlessService.getLinkedRequisition(requisitionId),
|
|
@@ -95,30 +94,31 @@ describe('goCardlessService', () => {
|
|
|
95
94
|
|
|
96
95
|
describe('#getRequisitionWithAccounts', () => {
|
|
97
96
|
it('returns combined data', async () => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
97
|
+
vi.spyOn(goCardlessService, 'getRequisition').mockResolvedValue(
|
|
98
|
+
mockRequisitionWithExampleAccounts,
|
|
99
|
+
);
|
|
100
|
+
vi.spyOn(goCardlessService, 'getDetailedAccount').mockResolvedValueOnce(
|
|
101
|
+
mockDetailedAccountExample1,
|
|
102
|
+
);
|
|
103
|
+
vi.spyOn(goCardlessService, 'getDetailedAccount').mockResolvedValueOnce(
|
|
104
|
+
mockDetailedAccountExample2,
|
|
105
|
+
);
|
|
106
|
+
vi.spyOn(goCardlessService, 'getInstitution').mockResolvedValue(
|
|
107
|
+
mockInstitution,
|
|
108
|
+
);
|
|
109
|
+
vi.spyOn(
|
|
110
|
+
goCardlessService,
|
|
111
|
+
'extendAccountsAboutInstitutions',
|
|
112
|
+
).mockResolvedValue([
|
|
113
|
+
{
|
|
114
|
+
...mockExtendAccountsAboutInstitutions[0],
|
|
115
|
+
institution_id: 'NEWONE',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
...mockExtendAccountsAboutInstitutions[1],
|
|
119
|
+
institution_id: 'NEWONE',
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
122
|
|
|
123
123
|
const response = await goCardlessService.getRequisitionWithAccounts(
|
|
124
124
|
mockRequisitionWithExampleAccounts.id,
|
|
@@ -146,18 +146,18 @@ describe('goCardlessService', () => {
|
|
|
146
146
|
describe('#getTransactionsWithBalance', () => {
|
|
147
147
|
const requisitionId = mockRequisition.id;
|
|
148
148
|
it('returns transaction with starting balance', async () => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
149
|
+
vi.spyOn(goCardlessService, 'getLinkedRequisition').mockResolvedValue(
|
|
150
|
+
mockRequisition,
|
|
151
|
+
);
|
|
152
|
+
vi.spyOn(goCardlessService, 'getAccountMetadata').mockResolvedValue(
|
|
153
|
+
mockAccountMetaData,
|
|
154
|
+
);
|
|
155
|
+
vi.spyOn(goCardlessService, 'getTransactions').mockResolvedValue(
|
|
156
|
+
mockTransactions,
|
|
157
|
+
);
|
|
158
|
+
vi.spyOn(goCardlessService, 'getBalances').mockResolvedValue(
|
|
159
|
+
mockedBalances,
|
|
160
|
+
);
|
|
161
161
|
|
|
162
162
|
expect(
|
|
163
163
|
await goCardlessService.getTransactionsWithBalance(
|
|
@@ -216,9 +216,9 @@ describe('goCardlessService', () => {
|
|
|
216
216
|
});
|
|
217
217
|
|
|
218
218
|
it('throws AccountNotLinkedToRequisition error if requisition accounts not includes requested account', async () => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
vi.spyOn(goCardlessService, 'getLinkedRequisition').mockResolvedValue(
|
|
220
|
+
mockRequisition,
|
|
221
|
+
);
|
|
222
222
|
|
|
223
223
|
await expect(() =>
|
|
224
224
|
goCardlessService.getTransactionsWithBalance({
|
|
@@ -352,67 +352,54 @@ async function getAccounts(
|
|
|
352
352
|
noTransactions = false,
|
|
353
353
|
) {
|
|
354
354
|
const sfin = parseAccessKey(accessKey);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
},
|
|
355
|
+
|
|
356
|
+
const headers = {
|
|
357
|
+
Authorization: `Basic ${Buffer.from(
|
|
358
|
+
`${sfin.username}:${sfin.password}`,
|
|
359
|
+
).toString('base64')}`,
|
|
361
360
|
};
|
|
362
|
-
|
|
361
|
+
|
|
362
|
+
const params = new URLSearchParams();
|
|
363
363
|
if (!noTransactions) {
|
|
364
364
|
if (startDate) {
|
|
365
|
-
params.
|
|
365
|
+
params.append('start-date', normalizeDate(startDate));
|
|
366
366
|
}
|
|
367
367
|
if (endDate) {
|
|
368
|
-
params.
|
|
368
|
+
params.append('end-date', normalizeDate(endDate));
|
|
369
369
|
}
|
|
370
|
-
|
|
371
|
-
params.push(`pending=1`);
|
|
370
|
+
params.append('pending', '1');
|
|
372
371
|
} else {
|
|
373
|
-
params.
|
|
372
|
+
params.append('balances-only', '1');
|
|
374
373
|
}
|
|
375
374
|
|
|
376
375
|
if (accounts) {
|
|
377
|
-
|
|
378
|
-
params.
|
|
379
|
-
}
|
|
376
|
+
for (const id of accounts) {
|
|
377
|
+
params.append('account', id);
|
|
378
|
+
}
|
|
380
379
|
}
|
|
381
380
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
options,
|
|
390
|
-
res => {
|
|
391
|
-
let data = '';
|
|
392
|
-
res.on('data', d => {
|
|
393
|
-
data += d;
|
|
394
|
-
});
|
|
395
|
-
res.on('end', () => {
|
|
396
|
-
if (res.statusCode === 403) {
|
|
397
|
-
reject(new Error('Forbidden'));
|
|
398
|
-
} else {
|
|
399
|
-
try {
|
|
400
|
-
const results = JSON.parse(data);
|
|
401
|
-
results.sferrors = results.errors;
|
|
402
|
-
results.hasError = false;
|
|
403
|
-
results.errors = {};
|
|
404
|
-
resolve(results);
|
|
405
|
-
} catch (e) {
|
|
406
|
-
console.log(`Error parsing JSON response: ${data}`);
|
|
407
|
-
reject(e);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
},
|
|
412
|
-
);
|
|
413
|
-
req.on('error', e => {
|
|
414
|
-
reject(e);
|
|
415
|
-
});
|
|
416
|
-
req.end();
|
|
381
|
+
const url = new URL(`${sfin.baseUrl}/accounts`);
|
|
382
|
+
url.search = params.toString();
|
|
383
|
+
|
|
384
|
+
const response = await fetch(url.toString(), {
|
|
385
|
+
method: 'GET',
|
|
386
|
+
headers,
|
|
387
|
+
redirect: 'follow',
|
|
417
388
|
});
|
|
389
|
+
|
|
390
|
+
if (response.status === 403) {
|
|
391
|
+
throw new Error('Forbidden');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const text = await response.text();
|
|
395
|
+
try {
|
|
396
|
+
const results = JSON.parse(text);
|
|
397
|
+
results.sferrors = results.errors;
|
|
398
|
+
results.hasError = false;
|
|
399
|
+
results.errors = {};
|
|
400
|
+
return results;
|
|
401
|
+
} catch (e) {
|
|
402
|
+
console.log(`Error parsing JSON response: ${text}`);
|
|
403
|
+
throw e;
|
|
404
|
+
}
|
|
418
405
|
}
|
|
@@ -33,20 +33,17 @@ describe('FilesService', () => {
|
|
|
33
33
|
accountDb.mutate('DELETE FROM files');
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
beforeAll(
|
|
36
|
+
beforeAll(() => {
|
|
37
37
|
accountDb = getAccountDb();
|
|
38
38
|
filesService = new FilesService(accountDb);
|
|
39
|
-
done();
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
beforeEach(
|
|
41
|
+
beforeEach(() => {
|
|
43
42
|
insertToyExampleData();
|
|
44
|
-
done();
|
|
45
43
|
});
|
|
46
44
|
|
|
47
|
-
afterEach(
|
|
45
|
+
afterEach(() => {
|
|
48
46
|
clearDatabase();
|
|
49
|
-
done();
|
|
50
47
|
});
|
|
51
48
|
|
|
52
49
|
test('get should return a file', () => {
|
package/src/app.js
CHANGED
|
@@ -108,10 +108,14 @@ function parseHTTPSConfig(value) {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export async function run() {
|
|
111
|
-
|
|
111
|
+
const openIdConfig = config?.getProperties()?.openId;
|
|
112
|
+
if (
|
|
113
|
+
openIdConfig?.discoveryURL ||
|
|
114
|
+
openIdConfig?.issuer?.authorization_endpoint
|
|
115
|
+
) {
|
|
112
116
|
console.log('OpenID configuration found. Preparing server to use it');
|
|
113
117
|
try {
|
|
114
|
-
const { error } = await bootstrap({ openId:
|
|
118
|
+
const { error } = await bootstrap({ openId: openIdConfig }, true);
|
|
115
119
|
if (error) {
|
|
116
120
|
console.log(error);
|
|
117
121
|
} else {
|
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);
|